diff --git "a/02.c++\347\254\224\350\256\260/01.C\350\257\255\350\250\200.md" "b/02.c++\347\254\224\350\256\260/01.C\350\257\255\350\250\200.md" new file mode 100644 index 0000000..27376f6 --- /dev/null +++ "b/02.c++\347\254\224\350\256\260/01.C\350\257\255\350\250\200.md" @@ -0,0 +1,6711 @@ +--- + +title: C语言 + +date: 2020-11-17 08:08:15 + +tags: C语言 + +categories: C语言 + +--- + +# 目录 + +[TOC] + + + +2020.11.17-12.20 + + + + + + + + + +# 0.基础知识 + + + + + + + + + +**unsigned int (unsigned long)** + +- 4字节8位可表达位数:2^32=42 9496 7296 +- 范围:0 ~ 42 9496 7295 (42*10^8) + +**int (long)** + +- 4字节8位可表达位数:2^32=42 9496 7296 +- 范围:-21 4748 3648 ~ 21 4748 3647 (21*10^8) + +**unsigned long (unsigned __int64)** + +- 8字节8位可表达位数:2^64=1844 6744 0737 0960 0000 +- 范围:0 ~ 1844 6744 0737 0955 1615 (1844*10^16) + +**long long (__int64)** + +- 8字节8位可表达位数:2^64=1844 6744 0737 0960 0000 +- 范围:-922 3372 0368 5477 5808 ~ 922 3372 0368 5477 5807 (922*10^16) + + + + + + + +`while(~scanf()):~`按位取反,`scanf`返回值为-1时值不合法,-1(1111...1111),-1按位取反结果为0循环终止 + + + +> `%d` —— 以带符号的十进制形式输出整数 +> +> `%o` —— 以无符号的八进制形式输出整数 +> +> `%x` —— 以无符号的十六进制形式输出整数 +> +> `%u` —— 以无符号的十进制形式输出整数 +> +> `%c` —— 以字符形式输出单个字符 +> +> `%s` —— 输出字符串 +> +> `%f` —— 以小数点形式输出单、双精度实数 +> +> `%e` —— 以标准指数形式输出单、双精度实数 +> +> `%g` —— 选用输出宽度较小的格式输出实数,科学技术输出 +> +> `%ld`用于long类型,`%lld`用于long long类型。 +> +> `%lu` `sizeof(array)` +> +> `%p` `&i` +> +> ` double`对应的是 `%lf` +> `float`对应的是 `%f` + + + +![截屏2020-10-27 上午9.37.36](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-10-27%20%E4%B8%8A%E5%8D%889.37.36.png) + + + + + +## 输入输出函数说明 + +## 1.printf函数 + +```C + + 头文件:stdio.h + 原型:int printf(const char *fromat, ...);//返回成功打印字符个数 + format :格式控制字符串 + ...:可变参数列表 + 返回值:输出字符的数量 +//使用printf函数,求解一个数字n的十进制表示的数字位数 + #include +int main(int argc,const char* argv[]){ + int n; + scanf("%d", &n); + n = printf("%d",n); + printf(" has %d digits! \n", n); + return 0; +} + +``` + + + +## 2.scanf函数 + +```c + + + 头文件:stdio.h + 原型:int scanf(const char *fromat, ...);//返回成功打印字符个数 + format :格式控制字符串 + ...:可变参数列表 + 返回值:成功读入参数的个数 + //读入一个字符串(可能包含空格),输出这个字符串中字符的数量, +#include +int main(int argc,const char* argv[]){ + char str[1000] = {0}; + int n; + while(scanf("%[^\n]s",str) != EOF){ + getchar(); + n = printf("%s", str); + printf(" has %d digits!\n", n); + } + return 0; +} + + +``` + +> 使用scanf时其会将末尾的回车符保留在缓存当中,所以在用其输入字符串时一定要加一个getchar去吃掉回车符 + +`~scanf()` + + + +## 3.sprinf,fprintf + +```c + + +// + +#include +int main(int argc,const char* argv[]){ + int n; + char str[1000] = {0}; +# if 0 + //标准输入 + scanf("%d", &n);//stdin + //标准输出 + printf("%d\n", n); +# endif + //stdout stderr + sprintf(str, "%d.%d.%d.%d", 192, 168, 1, 1); + //字符串拼接 + printf("str = %s\n", str); + FILE *fout = fopen("output", "w"); + fprintf(stdout, "stdout = %s\n", str); + fprintf(stderr, "stdoerr = %s\n", str); + + +//stdout(标准输出),输出方式是行缓冲。输出的字符会先存放在缓冲区,等按下回车键时才进行实际的I/O操作。 +//stderr(标准错误),是不带缓冲的,这使得出错信息可以直接尽快地显示出来。 + fprintf(stdout, "stdout = %s", str); + fprintf(stderr, "stdoerr = %s", str); + //stdoerr = 192.168.1.1stdout = 192.168.1.1% + //在Linux环境下我们可以发现第一次会输出stdoerr, + //是因为stdout将输出的字符串放到了缓冲区当中直到程序结束在将缓冲区中的数据刷新出来。 + fprintf(stdout, "stdout = %s\n", str); + fprintf(stderr, "stdoerr = %s", str); + //stdout = 192.168.1.1 + //stdoerr = 192.168.1.1% + //这个属于stdout输出的第二种情况即遇到换行符(“\n”)时输出,因此在本例中顺序输出了该结果 + return 0; +} +``` + + + + + +# 2.基本运算符 + + + +> `& ` 按位与运算符 +> +> `^` 异或 ==> 逆运算 自己是自己的逆运算 +> +> 0异或任何数,其结果=任何数:0 ^ 0=0,0 ^ 1=1 +> +> 1异或任何数,其结果=任何数取反:1 ^ 0=1,1 ^ 1=0 +> +> 任何数异或自己,等于把自己置0:x ^ x=0 +> +> ​ 1 0 1 0 +> +> ​ ^ 1 1 1 1 +> +> ​ ==> 0 1 0 1 , +> +> 2^3 = 1 2^1=3 3^1=2 +> +> `~` +> +> ~x ==> 如果 x 为 0,则得到 1,如果 x 是 1,则得到 0 +> +> + + + +> `<<`,`>>` 左移,右移 +> +> 32bit +> +> 左移: +> +> 十进制整数2(0010) 2<<1 ==>(0100) = 4 = 2 * 2 +> +> + + + + + +`math.h` + +1.`pow`函数:指数函数 + +原型:`double pow(double a, bouble b);` + +编译时需要`gcc -o ippanduan 1.pow.c -lm` + +2.`sqrt`函数:开平方根函数 + +原型:`double sqrt(double x);` + + + + + +`ceil`函数:上取整函数 + +`double ceil(double x);` + +`floor`函数:向下取整 + + + +`abs`函数:整数绝对值 + +头文件:`stdlib.h` + +原型:`int abs(int x);` + +`fabs`函数:实数绝对值函数 + +`log`函数:以`e`为底对数函数 + +`log10`函数:以`10`为底对数函数 + +- ![formula](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/u=506614216,773713317&fm=58.png) + + + + + +32bit + +int : `2 ^ (-31) ~ 2 ^ 31 - 1` + +`n % 2` ==` n & 1` + + + + + + + +`!!`逻辑归一化,`!!(x)`,,,如果x=3, `!(x) == 0` `!!(x)== 1`,归一化 + +`false` : `null` , `0`,`\0` + + + +分支结构:顺序、分支、循环结构 + + + +switch 不可以用于double、float + + + +```c +switch(expression) { + case constant-expression : + statement(s); + break; /* 可选的 */ + case constant-expression : + statement(s); + break; /* 可选的 */ + + /* 您可以有任意数量的 case 语句 */ + default : /* 可选的 */ + statement(s); +} + +``` + + + +**循环结构** + +> while +> +> while (表达式){ +> +> ​ 代码块; +> +> } +> +> do{ +> +> 代码块; +> +> } while (表达式); +> +> +> +> for (初始化; 循环条件; 执行后操作) { +> +> ​ 代码块; +> +> } + + + + + + + +# **3.程序流程控制方法** + + + +## 1.短路原则: + + + +> 要使(表达式1)&&(表达式2)运算结果为真则要求:表达式1,表达式2都为真,如果表达式1为假,则不计算表达式2了,因为此时已 +> 经确定(表达式1)&&(表达式2)运算结果不可能为真,这就是&&运算的短路特性。 + +> 要使(表达式1)||(表达式2)运算结果为假则要求:表达式1,表达式2都为假,如果表达式1为真,则不计算表达式2了,因为此时已经 +> 确定(表达式1)||(表达式2)运算结果不可能为假,这就是||运算的短路特性。 + + + + + +```C +int a =0; + int b = 0; + if( (a++) && (b++)) { + printf("true a = %d, b = %d \n", a, b); + }else{ + printf("false a = %d, b = %d \n", a, b); + } + +``` + +在第一个之后输出的数字输出空格 + +```c + for (int i = 0; i < 5; i++){ + //if (i != 0) printf(" "); + // if (i) printf(" "); + i && printf(" ");//逻辑与短路原则 + i == 0 || printf(" "); + printf("%d", i); + } + printf("\n"); +``` + + + + + +## 2.随机数 + +>srand()是随机数种子,s表示seed,种子的意思。 + +> rand()可以产生0到RAND_MAX(32767)之间的随机数,用rand()%x可以得到0到x之间的随机数。 + +> srand()和rand()都需要用到stdlib.h。 +> +> srand()的种子主要有两种,srand(1)和srand((unsigned)time(NULL)),前者生成的随机数每次都是相等的,后者则是根据现在在走的时间取的随机数,是真随机数。 + + + +```c +#include +#include +//伪随机数,结果固定 +int val = rand() % 100; +printf("%d", val); +//伪随机数,结果随机 +srand(time(0)); +int val = rand() % 100; +printf("%d", val); + +``` + + + + + + + +## 3.取模运算 + +```c +if (i % 2)//偶数 +if (i & 1)//优化 + +//统计偶数个数 +num += (val & 1); + + +``` + + + + + + + +## 4.循环结构代码演示 + + + + + +```c +//回文数 +#include +int rev_num(int n){ + if (n < 0) return 0; + int x = n; + int temp = 0; + while (x){ + temp = temp * 10 + x % 10; + x /= 10; + } + return temp == n; +} +int main(){ + int n; + scanf("%d", &n); + printf("%s\n", rev_num(n) ? "YES" : "NO"); + return 0; +} +``` + + + + +```c +//回文数 base进制下的回文数 0110 4 + 2 +#include +int rev_num(int n, int base){ + if (n < 0) return 0; + int x = n; + int temp = 0; + while (x){ + temp = temp * base + x % base; + x /= base; + } + return temp == n; +} +int main(){ + int n; + scanf("%d", &n); + printf("%s\n", rev_num(n, 2) ? "YES" : "NO"); + return 0; +} +``` + + + + + +```c +int main(){ + + int n; + scanf("%d", &n); + + int x = n; + int digit = 0; + while(x){ + x /= 10; + digit += 1; + }; + printf("%d has %d digits !\n",n , digit); + return 0; +} + +//bug +//0 has 0 digits ! +//因为没有执行while循环 + +int main(){ + + int n; + scanf("%d", &n); + + int x = n; + int digit = 0; + do{ + x /= 10; + digit += 1; + }while(x); + printf("%d has %d digits !\n",n , digit); + return 0; +} +``` + + + + + +## 5.判断日期的合法性 + +> +> +>#### 题目描述 +> +> 给出一个日期,请判断日期的合法性。特别注意一下几点: +> +>1. 年份是负数时,代表公元前,是合法的 +>2. 月份和日子是负数是非法的 +>3. 2月份要注意判断闰年的情况,闰年的判断标准是:能被4整除但不能被100整除,或者能被400整除 +> +>------ +> +>#### 输入 +> +> 输入 𝑦,𝑚,𝑑y,m,d三个整数,分别代表年月日。 +> +>#### 输出 +> +> 如果日期合法,输出『Yes』,否则输出『No』。 + +```c +#include + +int check(int y, int m, int d){ + if (m <= 0 || m > 12 || d <= 0) return 0; + int mouth[13] = {0, 31, 28, 31, 30 ,31, 30, 31, 31, 30, 31, 30, 31}; + if ((y % 4 ==0 && y % 100) || y % 400 == 0) mouth[2] += 1; + return d<= mouth[m]; +} + +int main(int argc,const char* argv[]){ + int y, m, d; + scanf("%d%d%d", &y, &m, &d); + printf("%s\n", check(y, m, d) ? "YES" : "NO"); + return 0; +} +``` + + + + + + + + + +>题目描述 +> +>所谓丑数,就是素因子中只含有 2、3、5 的数字。给出一个数字 N,请输出 N 以内所有丑数。特别的规定,1是第一个丑数。 +> +>输入一个整数 𝑁(1≤𝑁≤1000)N(1≤N≤1000) +> +> +> +> 从小到达输出 𝑁N 以内的所有丑数,包括 𝑁N,每行输出一个数字 。 + +------ + + + +//***********30 + +约数定理:N= 2^n+3^n+5^n + +```c +#include +int check(int n){ + while(n % 2 == 0) n/= 2; + while (n % 3) n/= 3; + while (n % 5) n/= 5; + return n == 1; +} +int main(){ + + int n; + scanf("%d",&n); + for(int i= 1;i <= n; i++){ + if (!check(i)) continue; + printf("%d\n", i); + + } + + return 0; +} +``` + + + + + +>`n % 2 ==0 ==> n & 1 ==0` 3%2 3&1 +> +>​ 0 0 1 1 +> +>& 0 0 1 0 +> +>------------- +> +>​ 0 0 1 0 +> +>3 = 2^1 + 2^0 结果值与最后一位有关系 +> +>`n % 4 == 0 => n & 3 == 0` +> +>`n % 4` : 结果值有:0 1 2 3 +> +>​ 0 1 0 0 +> +> 0 0 1 1 +> +>------------- +> +>​ 0 0 1 0 +> +>`n % 6` !==>任何东西,转换成二进制后,前面的数,不一定都能被6整除 +> +>`n % 7` +> +>`n % 8`==> `n & 7` +> +>`n % 16`==> `n & 15` +> +> + + + + + + + +# 4.函数 + +## 1.函数 + +在没有特殊函数情况下C/C++函数开始的入口是main函数 + +> 函数:压缩了的数组 +> +> 数组是展开的函数 + + + +## 2.递归 + +> 递归: +> +> ​ 程序调用自身的变成技巧 + + + +递归程序的组成部分: + +​ 1.边界条件处理 + +​ 2.针对问题的处理过程和递归过程 + +​ 3.结果返回 + + + +## 3.函数指针 + + + + + +```c +int f(int (*f1)(int), int (*f2)(int), int (*f3)(int), int x) { + if(x < 0){ + return f1(x); + } + if(x < 100){ + return f2(x); + } + return f3(x); + +} +``` + + + +函数指针的应用:分段函数 + +二分查找 + +`(head + tail) / 2 等价于 (head + tail) >> 1` + +```c +int binary_search(int *num, int n, int x){ + int head = 0; + int tail = n - 1; + int mid; + while (head <= tail) { + mid = (head + tail) / 2; + //mid = (head + tail) >> 1; + if(num[mid]==x) return mid; + if(num[mid] < x) head = mid + 1; + else tail = mid - 1; + } + return -1; +``` + + + + + + + +`*(num)(long long)`函数指针 + +```c +#include + +long long Triangle(long long n) { + return n * (n + 1) / 2; +} + +long long Pentagonal(long long n) { + return n * (3 * n - 1) / 2; +} + +long long Hexagonal(long long n) { + return n * (2 * n - 1); +} + +long long binary_search(long long (*num)(long long), long long n, long long x) { + long long head = 0, tail = n - 1, mid; + while (head <= tail) { + mid = (head + tail) >> 1; + if (num(mid) == x) return mid; + if (num(mid) < x) head = mid + 1; + else tail = mid - 1; + } + return -1; +} + +int main() { + int n = 285; + while (1) { + n++; + long long val = Triangle(n); + if (binary_search(Pentagonal, val, val) == -1) continue; + if (binary_search(Hexagonal, val, val) == -1) continue; + printf("%lld\n", val); + break; + } + return 0; +} +``` + + + + + +## 4.欧几里得算法 + +辗转相除法(计算两个数字的最大公约数) + + + + + +`while(~scanf())`:`~`按位取反,`scanf`返回值为-1时值不合法,-1(1111...1111),-1按位取反结果为0循环终止 + + + + + +```c +#include +int gcd(int a, int b) { +// if (!b) return a;//==> if (b == 0) return a; +// return gcd(b, a % b); + return (b ? gcd(b, a % b) : a); +} + +int main(int argc,const char* argv[]){ + int a, b; + while(~scanf("%d%d", &a, &b)) { + printf("gcd(%d, %d) = %d\n", a, b, gcd(a, b)); + } + return 0; +} +``` + + + +扩展:关键字`inline`内联函数 + +可能使函数调用更快,减少函数调用开销,递归函数和复杂的循环不能使用`inline` + + + + + +## 5.扩展欧几里得算法 + + + +快速求解`ax+by=1`的方程组的一组整数解 + + + +```c + +``` + + + +## 6.变参函数 + +实现可变参数 max_int ,从若干个参数中返回最大值 + +​ `int max_int(int a, ...);` + +如何获得a往后的参数列表?va_list类型的变量 + +如何定位a后面第一个参数的位置?va_start函数 + +如何获取下一个可变参数列表中的参数?va_arg函数 + +如何结束整个获得整个可变参数列表的动作?va_end函数 + + + +```c + +#include +#include +#include + +int max_int(int n, ...){ + int ans = INT32_MIN;//32位极小值 + va_list arg;//...参数与列表 + va_start(arg, n); + while (n--){ + int temp = va_arg(arg, int); + if (temp > ans) ans = temp; + } + va_end(arg); + return ans; +} + + +int main() { + printf("%d\n", max_int(3, 1, 2, 3)); + printf("%d\n", max_int(5, 1, 13, 3, 4, 7)); + printf("%d\n", max_int(5, 1, 13, 3, 4, 7, 21)); + return 0; +} + +3 +13 +13 +21a +``` + + + +## 7.简版printf函数实现 + + + +```c +#include + +int my_printf(const char *frm, ...) { + int cnt = 0; + for (int i =0; frm[i]; i++){ + putchar(frm[i]); + ++cnt; + } + return cnt; +} + +int main() { + printf("hello, world!\n"); + printf("my_printf = %d\n", my_printf("hello, world!\n")); + return 0; +} +``` + + + +`const char *frm` frm的值为常量 + +const字符串字面量 + +传入变量也没有任何影响 + + + +```c + +#include +#include +#include + +int my_printf(const char *frm, ...) { + int cnt = 0; + va_list arg; + va_start(arg, frm); + #define PUTC(a) putchar(a),++cnt + + for (int i =0; frm[i]; i++) { + switch (frm[i]) { + case '%': { + switch (frm[++i]) { + case '%': PUTC(frm[i]); break; + case 'd':{ + int x = va_arg(arg, int); + if (x < 0) PUTC('-'),x = -x; + int temp = 0; + int digit = 0; + do { + temp = temp * 10 + x % 10; + x /= 10; + ++digit; + } while(x); + while (digit--) { + PUTC(temp % 10 + '0'); + temp /= 10; + } + break; + } + } + break; + } + default : {PUTC(frm[i]); break;} + } + // putchar(frm[i]); + + } + + #undef PUTC + va_end(arg); + + return cnt; +} + +int main() { + printf("printf == > hello, world!\n"); + printf("my_printf ==> %d\n\n", my_printf("hello, world!\n")); + + int a = 123; + printf("int (a) = %d\n", a); + my_printf("my_printf ==> int (a) = %d\n\n", a); + + printf("int (0) = %d\n", 0); + my_printf("my_printf ==> int (0) = %d\n\n", 0); + + + printf("int (10000) = %d\n", 10000); + my_printf("my_printf ==> int (10000) = %d\n\n", 10000); + + printf("int (-123) = %d\n", -123); + my_printf("my_printf ==> int (-123) = %d\n\n", -123); + return 0; +} +``` + + + + + +优化 + + + +```c + +#include +#include +#include + +int reverse_num(int x,int *temp) { + int digit = 0; + do { + digit++; + *temp = *temp * 10 + x % 10; + x /= 10; + } while (x); + return digit; +} + +int output_num(int num, int digit) { + int cnt = 0; + while(digit--) { + putchar(num % 10 + '0'); + cnt++; + num /= 10; + } + return cnt; +} + +int my_printf(const char *frm, ...) { + int cnt = 0; + va_list arg; + va_start(arg, frm); + #define PUTC(a) putchar(a),++cnt + + for (int i =0; frm[i]; i++) { + switch (frm[i]) { + case '%': { + switch (frm[++i]) { + case '%': PUTC(frm[i]); break; + case 'd':{ + int x = va_arg(arg, int); + uint32_t xx = 0; + if (x < 0) PUTC('-'),xx = -x; + else xx = x; + int x1 = xx / 100000, x2 = xx % 100000; + int temp1 = 0, temp2 = 0; + int digit1 = reverse_num(x1, &temp1); + int digit2 = reverse_num(x2, &temp2); + int digit3 = 0; + if (x1) digit3 = 5 - digit2; + else digit1 = 0; + cnt += output_num(temp1, digit1); + cnt += output_num(0,digit3); + cnt += output_num(temp2, digit2); + } break; + case 's': { + const char *str = va_arg(arg, const char *); + for (int i = 0; str[i]; i++) { + PUTC(str[i]); + } + } + } + break; + } + default : {PUTC(frm[i]); break;} + } + + } + + #undef PUTC + va_end(arg); + + return cnt; +} + +int main() { + printf("printf == > hello, world!\n"); + printf("my_printf ==> %d\n\n", my_printf("hello, world!\n")); + + int a = 123; + printf("int (a) = %d\n", a); + my_printf("my_printf ==> int (a) = %d\n\n", a); + + printf("int (0) = %d\n", 0); + my_printf("my_printf ==> int (0) = %d\n\n", 0); + + + printf("int (10000) = %d\n", 10000); + my_printf("my_printf ==> int (10000) = %d\n\n", 10000); + + printf("int (10050) = %d\n", 10050); + my_printf("my_printf ==> int (10050) = %d\n\n", 10050); + + printf("int (-123) = %d\n", -123); + my_printf("my_printf ==> int (-123) = %d\n\n", -123); + + printf("int (INT32_MAX) = %d\n", INT32_MAX); + my_printf("my_printf ==> int (INT32_MAX) = %d\n\n", INT32_MAX); + + printf("int (INT32_MIN) = %d\n", INT32_MIN); + my_printf("my_printf ==> int (INT32_MIN) = %d\n\n", INT32_MIN); + + char str[] = "I love China"; + printf("(str) = %s\n", str); + my_printf("my_printf ==> str = %s\n\n", str); + + + int n; + while (~scanf("%d", &n)) { + printf(" has %d digits!\n", printf("printf %d", n) - 7); + my_printf(" has %d digits!\n\n", my_printf("my_printf %d", n) - 10); + + } + return 0; +} +``` + + + + + + + + + +# 5.数组 + + + +## 1.数组声明与初始化 + + + +`int arr[100] = {0}`数组清空,只有={0}时,全部的数组才能被初始化为0, + +静态数组在内存栈区(8M) + +动态数组molloc,calloc,rolloc + + + +数组是展开的函数 + + + +## 2数组的运用 + +### 素数筛 + +时间复杂度`O(N* loglogN)`趋近于`O(N)` + +> 思想:用素数标记掉不是素数的数字,例如我知道了i是素数,那么`2*i`,`3*i`...不是素数 + + + + + +```c +#include +#define max_n 100 + +int prime[max_n + 5] = {0}; +void init() { + for (int i = 2; i <= max_n; i++) { + if (prime[i]) continue;//if (prime[i] == 1){} + for (int j = 2 * i; j <= max_n; j += i) { + prime[j] = 1; + } + } +} +int main(int argc,const char* argv[]){ + init(); + for (int i = 2; i <= max_n; i++) { + if (prime[i]) continue; + printf("%d\n", i); + } + return 0; +} +``` + + + +**优化** + +```c +#include +#define max_n 10000 + +int prime[max_n + 5] = {0}; +void init() { + for (int i = 2; i <= max_n; i++) { + if (prime[i]) continue; + prime[++prime[0]] = i;///******!!! + for (int j = i * i; j <= max_n; j += i) { + //当 i = 10000是发生溢出 + prime[j] = 1; + } + } +} +int main(int argc,const char* argv[]){ + init(); + for (int i = 2; i <= prime[0]; i++){ + printf("%d\n", prime[i]); + } + return 0; +} +``` + + + +> `prime[++prime[0]] = i;` +> +> 会不会出现已经存储的还没有被标记 +> +> 标记的速度很快,标记一群存储一个 +> +> 3是素数,4是偶数,基数偶数相邻,除2以外,偶数都不是素数, + + + + + +### 线性筛 + +时间复杂度O(n) + + + +```c + +#include +#define max_n 10000 + +int prime[max_n + 5]; + +void init() { + for (int i = 2; i <= max_n; i++) { + if (!prime[i]) prime[++prime[0]] = i; + //如果没有值,给他前面一个合数赋值 + for (int j = 1; j <= prime[0]; j++) { + if (prime[j] * i > max_n) break; + prime[prime[j] * i] = 1; + if (i % prime[j] == 0) break; + } + } +} + +int main(int argc,const char* argv[]){ + init(); + for (int i = 1; i <= prime[0]; i++) { + printf("%d\n", prime[i]); + } + printf("prime[0] = %d\n", prime[0]); + return 0; +} +``` + + + + + + + + + +## 3.折半查找/二分查找 + + + + + + + +数组转函数,剩下开辟数组空间的时间,时间换空间 + + + +```c +#include + +int binary_search(int (*arr)(int), int x, int n) { + int head = 0; + int tail = n - 1; + int mid ; + while (head <= tail) { + mid = (tail + head ) / 2; + if (arr(mid) == x) return mid; + if(arr(mid) > x) { + mid -= 1; + tail = mid; + } + else { + mid += 1; + head = mid; + } + } + + return -1; +} + +int binary_search1(int *arr, int x, int n) { + int head = 0; + int tail = n - 1; + int mid; + while(head <= tail) { + mid = (head + tail) >> 1; + if (arr[mid] == x) return mid; + if (arr[mid] < x) head = mid + 1; + else tail = mid - 1; + } + + return -1; +} +int func(int x) { + return x * x; +} +int main() +{ + int arr[100] = {0}; + int n; + scanf("%d", &n); + for (int i = 0; i < n; i++) { + arr[i] = i * i; + //scanf("%d", &arr[i]); + } + int x; + while(~scanf("%d", &x)) { + printf("binary_search ==>%d\n", binary_search(func, x, n)); + + printf("binary_search1==> %d\n", binary_search1(arr, x, n)); + } + + return 0; +} +``` + + + + + +>(2 + 4) / 2 +> +>0010 +> +>0100 +> +>0111 ==> >>1 == 0011 = 3 ,最后一位为1或0, +> +> + + + +折半查找实现平方根 + + + +```c +#include +#include + +double func(double x) { + return x * x; +} + +double binary_search(double (*arr)(double), double n) { + double head = 0; + double tail = n; + double mid; + + if (n < 1.0) tail = 1.0; + #define EPLS 1e-8 + while (tail - head > EPLS) { + mid = (head + tail) / 2.0; + if(arr(mid) < n) head = mid; + else tail = mid; + } + #undef EPLS + return head; + +} + +int main() +{ + double n; + scanf("%lf", &n); + printf("my_sqrt(%lf) = %g\n", n, binary_search(func, n)); + printf("sqrt(%lf) = %g\n", n, sqrt(n)) ; + return 0; +} +``` + + + +## 4.牛顿迭代 + + + +> x2= x1 - (f(x1))/f`(x1) + + + + + +牛顿迭代递推式: + +![截屏2020-12-10 上午9.47.46](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-12-10%20%E4%B8%8A%E5%8D%889.47.46.png) + + + + + + + + + +```c +#include +#include +double func(double x, double n) { + return x * x - n; +} + +double f(double x) { + return 2 * x; +} + +double NewTon(double (*F)(double,double), double (*f)(double), double n) { + double x = n / 2.0; + #define EPSL 1e-7 + while (fabs(F(x, n)) > EPSL) { + x -= F(x, n) / f(x); + } +#undef EPSL + return x; +} +double my_sqrt(double n) { + return NewTon(func, f, n); +} +int main() +{ + double n; + scanf("%lf", &n); + printf("sqrt(%g) ==>%g\n", n, sqrt(n)); + printf("my_sqrt(%g) ==> %g\n", n, NewTon(func, f, n)); + printf("mysqrt %g==> %g\n", n, my_sqrt(n)); + + return 0; +} +``` + + + + + + + + + +## 5.数组代码演示 + +静态数组 int arr[100];,存放在栈区 + +动态数组 通过molloc动态开辟空间 ,存放在堆区 + + + + + +```c +#include +#define max_n 100 + + + +int arr[max_n +5]; +//当将数组放在main函数外面的时候,数组的默认值为0 +int main(){ + + //变量存放在栈区 + int arr[max_n +5] = {0};//静态数组 + //将arr的每一位值都初始化为0,不过不初始化为0,默认值不一定为0 + //函数内部申明变量,放在栈区 8MB,初始化值太大会发生爆栈 + + + scanf("%d", &n); + //为什么传地址: + //程序的功能逻辑需要传变量的地址, + //通过scanf写入值,通过scanf函数改变函数外部变量的值,需要传入地址 + //传出参数 + for (int i = 0; i < n; i++) { + //scanf("%d", &arr[i]); + //等价于 + scanf("%d", arr + i); + + } + for (int i = 0; i < n; i++) { + printf("%d ", arr[i]); + } + printf("\n"); + return 0; + +}  +``` + + + + + + + +## 6.数组传参 + + + +```cpp +#include +#define max_n 100 + + //printf("%d\n",arr[10]; +int main(){ + + + int arr[max_n +5] = {0}; + + //105 * 4 = 420 bit + int n; + + printf("sizeof(arr) = %lu\n", sizeof(arr)); + //420 , 4 * 105 = 420 + //sizeof(arr)输出arr的所占空间大小 + + printf("arr = %p, &arr[0] = %p\n", arr, &arr[0]);//arr == &arr[0] + printf("arr[1] = %p\n", &arr[1]); + char str[max_n + 5]; + printf("str = %p, str + 1 = %p\n", str, str + 1);//1bit + //arr[1] - arr[0] ==> 4bit,1个int 4 bit + // arr + 1 跳跃一个元素((int)bit))的大小 + //double 8 bit +/* + int32_t <==> int 4 bit + long / long long 64 bit + int64_bit + */ + + return 0; +} +``` + + + + + + + + + +```cpp +#include +#define max_n 100 + +void func(int *num) { + //1.指针变量也是变量,存储地址,指针存储地址 + //2.传出参数,希望外部的函数也可以访问改变的变量 + + + printf("sizeof(num) = %lu\n", sizeof(num)); + //num=8bit arr=420bit + //那出传进来的是数组还是指针变量, + //num表现形式是指针,int类型指针,64位系统占8bit,所以传进来的是一个指针变量, + //num接收数组首地址 + + //表现形式一致性 + //num arr不一样,表现形式一致, + //arr + i 通过地址访问arr[i],,num+i可以访问nnum[i] + printf("num = %p, num + 1 = %p\n", num, num + 1); + //(num + 1) - num = 4 bit + //(arr + 1) - arr = 4 bit ===>表现形式一致 + +} + +void func1(int *n) { + //*n = 123; + //等价于 + n[0] = 1234; +} + + +int main(){ + + int arr[max_n +5] = {0}; + printf("sizeof(arr) = %lu\n", sizeof(arr)); + //420bit + func(arr); + + n = 0; + printf("n = %d\n", n);//0 + scanf("%d", &n); + printf("n = %d\n", n);// + func1(&n); + printf("n = %d\n", n);//*n=123 n[0]=1234 + return 0; + +} +``` + + + + + +二维数组传参 + +```cpp +#include + + +void func(int (*num)[4]) {// num -> int[4] + printf("arr = %p, arr + 1 = %p\n", num, num + 1);//16bit +} +void func1(int **num) { + printf("num = %p, num + 1 = %p\n", num, num + 1);//8 bit +} +int main() +{ + int arr[2][4] = {0}; + + func(arr); + printf("arr = %p, atrr + 1 = %p\n", arr, arr + 1);//16bit + int **num;//指针的指针 + + func1(num);//8 bit + + return 0; +} + + +``` + + + + + + + +![截屏2020-12-11 下午3.26.43](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-12-11%20%E4%B8%8B%E5%8D%883.26.43.png) + + + + + + + +高维数组传参,省略最前面的一个 + +![截屏2020-12-11 下午3.42.42](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-12-11%20%E4%B8%8B%E5%8D%883.42.42.png) + + + + + + + +# 6.预处理命令 + +预处理命令-宏定义 + + + +> 以`#`开头的都是C语言的预处理命令 + + + + + +## 1.定义符号常量 + +`#define PI 3.14` + +`#define MAX_N 10000` + + + + + +## 2.定义傻瓜表达式 + +`#define MAX(a,b) (a) > (b) ? (a) : (b)` + +`#define S(a, b) a * b` + +> +> +> `==> S(1 + 3, 4 + 2) == 1 + 4 * 4 + 2` +> +> ​ `!= (1 + 3) * (4 + 2)` + +> **宏:仅做符号替换,不做计算,只可以定义一行** + + + + + +## 3.定义代码段 + + + +> `#define P(a) {\` +> +> ``printf("%d\n", a);\` +> +> `}` +> +> `\`连接符,分行写,将当前行的代码与下一行的代码连接 + + + +## 4.预处理命令-预定义宏 + + + +| 宏 | 说明 | +| :--------------------: | ----------------------------- | +| `__DATE__` | 日期:M mm dd yyyy | +| `__TIME__` | 时间:hh:mm:ss | +| `__LINE__` | 行号 | +| `__FILE__` | 文件名 | +| `__func__` | 函数名/***非标准*** | +| `__FUNC__` | 函数名/***非标准*** | +| `__PRETTY__FUNCTION__` | 更详细的函数信息/***非标准*** | + + + + + +`__DATE__ 、__TIME__`:编译时的日期 + +非标准:在不同的环境下名字可能不同 + + + +可以做: + +确定最后一次编译时的时间 + +行号、文件、文件名:打印日志功能 + + + + + +## 5.预处理命令-条件式编译 + + + +| 函数 | 说明 | +| :----------------: | :-------------------------------------------------- | +| `#ifdef DEBUG` | 是否定义了DEBUG宏,作用于当前宏后面的代码 | +| `#ifdef DEBUG` | 是否没定义DEEBUG宏 | +| `#if MAX_N == 5` | 宏MAX_N是否等于5,MAX_N是宏不是变量,作用时期不一样 | +| `#elif MAX_N == 4` | 否则宏MAX_N是否等于4 | +| `#else` | | +| `#endif` | //结束宏 | + + + + + +> MAX_N不是变量是宏, +> +> 条件式编译解决非标准预定义宏,实现代码剪裁 + + + + + + + +![截屏2020-12-14 上午10.27.57](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-12-14%20%E4%B8%8A%E5%8D%8810.27.57.png) + + + + + +> C源文件:vim a.cpp +> +> ↓(预处理阶段 : 处理预处理命令,将所有的预处理预处命令进行展开和替换,生成代码) +> +> 编译源码 : 编译期 +> +> ↓(一个.c/.cpp生成一个对象(.o/.obj)文件,如果有多个.cpp文件会生成多个对象文件) +> +> 对象文件: 生成.o/.obj(win)文件 +> +> ↓链接期:链接过程(所有的对象文件生成一个可执行文件) +> +> 可执程序: 默认生成a.out(可被执行的二进制文件) +> +> +> +> 预处理期 →编译期 →链接期 + + + + + + + +> 练习1:**实现打印LOG函数,需要输出所在函数及行号等信息** + + + +```c +#include + +//#define DEBUG +#ifdef DEBUG +#define log(frm ,args...) {\ + printf("%s ==> %s : %d]", __FILE__, __func__, __LINE__);\ + printf(frm, ##args);\ +} + +#else +#define log(frm, args...) +#endif + +//#args将args(任意类型都可)转换为字符串 +//如果lon传入的参数为1个,变参列表就为空,编译器会报警告,too manny argument.. +//##args连接,如将a和b连接为ab,连接时后面的可以为空 + +//#define concat(a, b) a##b +//如果去掉##,写ab的意思是将a、b变量替换成字符ab + +int main() +{ + + int a = 123; + printf("printf==>hello 古子秋\n"); + log("log==>hello apricity\n"); + log("lon(a)==>%d\n", a); + int abc = 2; + int def = 3; + int abcdef = 0; +// concat(abc, def) = 234; +// log("abcdef = %d\n", abcdef); + + return 0; +} + +``` + + + +`g++ -DDEGBUG 32.LOG.c` + + + + + +> 练习二:**实现没有BUG的MAX宏,需要通过如下测试** +> +> 1、MAX(2, 3)//3 +> +> 2、 5 + MAX(2, 3)//8 +> +> 3、MAX(2, MAX(3, 4))//4 +> +> 4 、MAX(2, 3 > 4 ? 3 : 4)//4 +> +> 5、 MAX(a++, 6), a = 7,return 8,a= 8// + + + +`gcc -E 28.max.c`将所有的预处理命令展开生成代码 + + + +`__typeof(a)`获取a的类型 + +int a == > typeof(int) + + + +```c +#include + +#define MAX(a,b) a > b ? a : b +//debug1提升优先级 +#define MAX(a,b) (a > b ? a : b) +//debug2加上括号,防止与后面的内容发生关联 +#define MAX(a,b) ((a) > (b) ? (a) : (b)) +//debug3 +#define MAX(a,b) ({\ + __typeof(a) _a = (a);\ + __typeof(b) _b = (b);\ + _a > _b ? _a : _b;\ + }) + +//__typeof(a) 根据值替换为该值的类型 + +#define P(func) {\ + printf("%s = %d\n", #func, func);\ +} +//#func将func里面的内容转换为字符串 + +int main() { + + int a = 7; + P(MAX(2, 3)); + P(5 + MAX(2, 3)); + P(MAX(2, MAX(3, 4))); + + P(MAX(2, 3 > 4 ? 3 : 4)); + P(MAX(a++, 6)); + P(a); + + + return 0; +} + +//gcc -E 28.max.c + + +int main() { + + int a = 7; + { printf("%s = %d\n", "MAX(2, 3)", 2 > 3 ? 2 : 3);}; + { printf("%s = %d\n", "5 + MAX(2, 3)", 5 + 2 > 3 ? 2 : 3);}; + //(5 + 2) > 3 ? 2 : 3 = 7 > 3 ? 2 ; 3 = 2 !!!!!!! + //5 + (2 > 3 ? 2 : 3) = 5 + 3 = 8 + { printf("%s = %d\n", "MAX(2, MAX(3, 4))", 2 > 3 > 4 ? 3 : 4 ? 2 : 3 > 4 ? 3 : 4);}; + + { printf("%s = %d\n", "MAX(2, 3 > 4 ? 3 : 4)", 2 > 3 > 4 ? 3 : 4 ? 2 : 3 > 4 ? 3 : 4);}; + //2 > 3 > 4 ? 3 : 4 ? 2 : 3 > 4 ? 3 : 4 + // 0 > 4 ? 3 : 4 ? 2 : 3 > 4 ? 3 : 4 + //4 ? 2 : 3 > 4 ? 3 : 4 + //2 + //与后面的内容发生关联 + { printf("%s = %d\n", "MAX(a++, 6)", a++ > 6 ? a++ : 6);}; + //a增加了两次 debug,将a++替换出来, + { printf("%s = %d\n", "a", a);}; + + + return 0; +} + + + +./a.out + +MAX(2, 3) = 3//√ + //debug1 +5 + MAX(2, 3) = 2//× =8 + //debug2 +MAX(2, MAX(3, 4)) = 2//× 4 +MAX(2, 3 > 4 ? 3 : 4) = 2//× 4 + //debug3 +MAX(a++, 6) = 8 +a = 9 +``` + + + +```c +#include + +#define MAX(a,b) ({\ + __typeof(a) _a = (a);\ + __typeof(b) _b = (b);\ + _a > _b ? _a : _b;\ + }) + +#define P(func) {\ + printf("%s = %d\n", #func, func);\ +} + +int main() { + + int a = 7; + P(MAX(2, 3)); + P(5 + MAX(2, 3)); + P(MAX(2, MAX(3, 4))); + + P(MAX(2, 3 > 4 ? 3 : 4)); + P(MAX(a++, 6)); + P(a); + return 0; +} +``` + + + + + +debug后 + +```c +gcc -E 28.max.c + +int main() { + + int a = 7; + { printf("%s = %d\n", "MAX(2, 3)", ({ __typeof(2) _a = (2); __typeof(3) _b = (3); _a > _b ? _a : _b; }));}; + { printf("%s = %d\n", "5 + MAX(2, 3)", 5 + ({ __typeof(2) _a = (2); __typeof(3) _b = (3); _a > _b ? _a : _b; }));}; + { printf("%s = %d\n", "MAX(2, MAX(3, 4))", ({ __typeof(2) _a = (2); __typeof(({ __typeof(3) _a = (3); __typeof(4) _b = (4); _a > _b ? _a : _b; })) _b = (({ __typeof(3) _a = (3); __typeof(4) _b = (4); _a > _b ? _a : _b; })); _a > _b ? _a : _b; }));}; + + { printf("%s = %d\n", "MAX(2, 3 > 4 ? 3 : 4)", ({ __typeof(2) _a = (2); __typeof(3 > 4 ? 3 : 4) _b = (3 > 4 ? 3 : 4); _a > _b ? _a : _b; }));}; + { printf("%s = %d\n", "MAX(a++, 6)", ({ __typeof(a++) _a = (a++); __typeof(6) _b = (6); _a > _b ? _a : _b; }));}; + { printf("%s = %d\n", "a", a);}; + + + return 0; +} + +./a.out + +MAX(2, 3) = 3 +5 + MAX(2, 3) = 8 +MAX(2, MAX(3, 4)) = 4 +MAX(2, 3 > 4 ? 3 : 4) = 4 +MAX(a++, 6) = 7 +a = 8 +``` + + + + + + + + + + + +> **函数从哪里开始????** + + + +```c +#include + +__attribute__((constructor)) +int add(int a, int b) { + printf("add : %d\n", __LINE__); + return a + b; +} + +int main() +{ + printf("main : %d\n", __LINE__); + add(2,3); + return 0; +} + +``` + + + +>add : 5 +>main : 11 +>add : 5 + + + +> 默认情况下从main函数开始执行 +> +> `__attribute__((constructor))` +> +> : 预处理命令——宏,给函数增加一个属性,使下面的函数先于主函数执行 + + + + + + + +宏嵌套 + +宏支持嵌套,可能会出现停止展开的现象 + +输出带颜色的符号 + +宏的作用:作用时期早,不涉及调用函数时间损耗 + +```c +#include + +#define COLOR(a,b) "\033[" #b "m" a "\033[0m" +//a本身就是一个字符串 +//进行字符串拼接, +#define RED(a) COLOR(a, 31) +#define GREEN(a) COLOR(a, 32) +//宏所有的事情只是简单的替换、 + +__attribute__((constructor)) +int add(int a, int b) { + printf(RED("add : %d\n"), __LINE__);//输出红色 + return a + b; +} + +int main() +{ + printf(GREEN("main : %d\n"), __LINE__);//输出绿色 + add(2,3); + return 0; +} + + +``` + + + +运行结果: + +![截屏2020-12-12 下午6.51.55](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-12-12%20%E4%B8%8B%E5%8D%886.51.55.png) + + + + + + + +> 泛型宏? + + + +# 7.字符串 + + + + + +定义字符串数组:`char str[size];` + + + +初始化字符数组: + +> `char str[ ] = "hello world";` +> +> //str占多少字节:一个字符一个字节,12个(11 + '\0')长度为11 +> +> `char str[size] = {'h', 'e', 'l', 'l', 'o'};` + + + +\0 == 0 + +头文件`string.h` + + + +| 函数 | 说明 | +| ------------------------- | --------------------------------- | +| `strlen(str)` | 计算字符串长度,以'\0'作为结束符 | +| `strcmp(str1, str2);` | 字符串比较,cpmpare | +| `strcpy(dest, src);` | 字符串拷贝copy(假如字符\0丢失) | +| `strncmp(str1, str2, n);` | 安全的字符串比较 | +| `strncpy(str1, str2, n);` | 安全的字符串拷贝(拷贝n个) | +| `memcpy(str1, str2, n);` | 内存拷贝(str2拷贝到str1,拷贝n位) | +| `memcmp(str1, str2, n);` | 内存比较 | +| `memset(str1, c, n);` | 内存设置(str每一个字节设置为c) | + + + + + +> `strcmp(str1, str2);` 字符串比较 +> +> `strcpy(dest, src);` 字符串拷贝 +> +> `strncmp(str1, str2, n);` `安全的字符串比较` +> +> `strncpy(str1, str2, n);` `安全的字符串拷贝` +> +> 字符串拷贝 +> +> src 拷贝到 str,src字符\0丢失,会一直拷贝 +> +> 如果'\0'丢失,strncpy会拷贝到第n位 +> +> + + + + + + + +| 函数 | 说明 | +| ---------------------------- | -------------------- | +| `sscanf(str1, format, ...);` | 从字符串str1读入内容 | +| `sprintf(str1,fromat, ...);` | 将内容输出到str1 | + + + + + +`fscanf`从文件读入 + +`fprintf`读入到文件 + + + +> 练习:使用字符串相关操作,计算一个整型16进制表示的位数 + + + +```c +#include +#include + +int main() +{ + char str[11] = {0}; + int n; + while(~scanf("%d", &n)) { + sprintf(str, "%x", n); + //将n转换为16进制,以字符串的形式读入str + //%X和%x的区别,%X输出结果为字母时是大写,%x是小写 + printf("%s has %lu digits!\n", str, strlen(str));//输出str长度即为16整型的位数 + } + + return 0; +} +``` + + + + + + + +> 注: +> +> %c读入单个字符,可以读入任何字符,包括空格符和换行, +> +> %s读入一个字符串 +> +> %c循环读入的时候发生错误,会将换行和空格符读入 +> +> 解决办法:以字符数组str[10]的形式以%s读入字符串,输出str[0]即可 +> +> + + + +![截屏2020-12-14 下午8.39.08](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-12-14%20%E4%B8%8B%E5%8D%888.39.08.png) + + + + + + + + + + + + + +# 8.复杂结构体 + + + + + +## 1.结构体struct + + + +```c +struct person {// struct + 结构体名字 + char name[20]; //姓名 20bit + int age; //年龄 4 + char gender; //性别 1 + float height; //身高 4 +}; +``` + + + + + +> struct person // 结构体类型,没有结构体的名字时,称为匿名结构体 +> struct person stu; //结构体类型的变量 +> stu.age; //`.`直接引用,通过类型的变量直接访问用'`.`'直接访问 +> stu->age; //`->`间接引用,需要通过地址访问地址下某一字段的值,需要间接访问 +> struct person *p; +> p = &stu; +> p->age; + + + + + + + +>//结构体空间划分标准:对齐标准 +> +> +>```c +>char name[20]; //姓名 1bit 5块 +>int age; //年龄 4 1 +>char gender; //性别 1 1 +>float height; //身高 4 1 +>``` +> +>对齐标准: +> +>按照对齐标准的整数倍开辟空间, +> +>哪个**类型**所占空间字节数大就作为对齐标准, +> +>以4bit为标准开辟空间, +> +>name 20bit → 开辟连续的五块4bit空间 +> +> 一共开辟8 * 4 = 32bit + + + +>```c +>char name[20]; //姓名 1bit 3块(24 = 20 + 4) +>int age; //年龄 4 0(age用name剩下的4bit) +>char gender; //性别 1 1 +>double height; //身高 8 1 +>``` +> +>5 * 8 = 40bit + + + + + +对齐标准不是固定的 + +>```c +>struct node1 { +> char a; //1(4bit(使用了1bit剩下3bit)) +> char b; //0(使用开辟a时剩下的1bit) +> int c; //1 +>} +>struct node2 { +> char a; //1 +> int c; //1 +> char b; //1 +>} +>``` +> +>node1 8bit +> +>node2 12bit +> +>划分空间时从上至下依次划分空间 +> +>==>相同类型的字段放在一起排布,可以节省空间 + + + +宏:强制改变对齐方式 + + + +对齐的作用,方便访问 + + + + + +## 2.共用体/联合体 + +> 共用体:当前公用体内的各个字段共用一片存储单元 + + + +> 共用体的大小就是共用体内所有字段中所占空间最大的字段 + + + +```c +union reginster { + struct { + unsigned char byte1; //1bit + unsigned char byte2; + unsigned char byte3; + unsigned char byte4; + } bytes; + unsigned int number; //4bit +} //共用体占4bit +``` + + + +![截屏2020-12-18 上午1.47.10](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-12-18%20%E4%B8%8A%E5%8D%881.47.10.png) + + + + + +找占空间最大的作为整个共用体的标准 + + + +![截屏2020-12-16 下午11.47.50](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-12-16%20%E4%B8%8B%E5%8D%8811.47.50.png) + +改变bytes的值,number也会改变 + + + + + +共用体的作用 + +使用共用体实现ip转整数 + +```c +#include + +union IP { + struct { + unsigned char a1; + unsigned char a2; + unsigned char a3; + unsigned char a4; + } ip; + unsigned int num; +}; + + +int main() +{ + union IP p; + char str[100] = {0}; + int arr[4]; + while (~scanf("%s", str)) {//输入 + sscanf(str, "%d.%d.%d.%d", arr, arr + 1, arr + 2, arr + 3); + //将str中的字符串以%d格式读如到arr中 + p.ip.a1 = arr[0]; + p.ip.a2 = arr[1]; + p.ip.a3 = arr[2]; + p.ip.a4 = arr[3]; + printf("%u\n", p.num); + //由于是共用体,四个unsigned char类型和unsigned int类型占空间一样 + } + return 0; +} + + +192.168.0.1 +16820416 +192.168.0.2 +33597632 + +``` + + + +![截屏2020-12-18 上午1.47.24](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-12-18%20%E4%B8%8A%E5%8D%881.47.24.png) + + + + + +> 大端小端 +> +> 小端机:数字低位→存储在低地址位 +> +> 大端机:数字低位→存储在高地址位 +> +> 内存地址以字节为单位编址, +> +> + +![截屏2020-12-17 上午12.14.10](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-12-17%20%E4%B8%8A%E5%8D%8812.14.10.png) + + + + + + + +![](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-12-17%20%E4%B8%8A%E5%8D%8812.15.56.png) + + + + + +```c +#include + +union IP { + struct { + unsigned char a1; + unsigned char a2; + unsigned char a3; + unsigned char a4; + } ip; + unsigned int num; +}; + + +int main() +{ + union IP p; + char str[100] = {0}; + int arr[4]; + while (~scanf("%s", str)) { + sscanf(str, "%d.%d.%d.%d", arr, arr + 1, arr + 2, arr + 3); + p.ip.a1 = arr[3]; + p.ip.a2 = arr[2]; + p.ip.a3 = arr[1]; + p.ip.a4 = arr[0]; + //反向存储,小端机数字低位→存储在低地址位 + //将ip地址首位存到低地址位上 + printf("%u\n", p.num); + } + return 0; +} + + + +192.168.0.1 +3232235521 +192.168.0.2 +3232235522 +``` + + + + + +```c +//判断大端机还是小端机 + +#include +int is_little() { + int num = 1; + return ((char *)(&num))[0]; + //将num强制转换为char类型,取其低位地址,如果为1则为小端机 +} + +int main() { + printf("%d\n", is_little()); + //返回1,小端机,返回0大端机 + return 0; +} +``` + + + + + + + +# 9.指针 + +## 0.指针含义 + +> 指针:变量的地址 + + + +> 64位操作系统指针占8字节,32问操作系统占4字节 + + + +> 指针变量也是变量:是变量就可以存储值,指针变量存储地址,变量本身也有地址 +> +> 指向整型的指针变量占8个字节 +> +> 是变量就会占用空间 +> +> 变量的地址为字节中最小的地址 +> +> `int a;` +> +> `int *p = &a; ` +> +> `*`取值操作,*p取地址里面的值 +> +> p是指针变量 +> +> + + + + + +![截屏2020-12-17 上午1.33.00](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-12-17%20%E4%B8%8A%E5%8D%881.33.00.png) + + + + + + + +![截屏2020-12-17 上午1.34.00](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-12-17%20%E4%B8%8A%E5%8D%881.34.00.png) + + + +是变量:变量的作用是用来存储值的,指针变量能接收到的值是地址 + +变量本身有地址,指针变量本身也有地址。 + + + + + +指向指针的指针 + +```c +int **q;// +struct person stu; +int *p; +p = stu; +//p+1向后跳跃了一个类型的字节 + +``` + + + + + + + + + +## 2.指针等价形式转换 + +![截屏2020-12-20 下午4.23.27](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-12-20%20%E4%B8%8B%E5%8D%884.23.27.png) + + + +```cpp +#include +using std::cin; +using std::cout; +using std::endl; +struct Data{ + int x, y; +}; + + +int main() { + struct Data a[2], *p = a; + a[0].x = 3;a[0].y = 3; + a[1].x = 5;a[1].y = 5; + + cout << endl; + cout << a[1].x << endl; + + cout << (*(a+1)).x << endl; + cout << (*(&(a[0]) + 1)).x << endl; + + cout << (a + 1)->x << endl; + cout << (*(a + 1)).x << endl; + + cout << (*(&a[0] + 1)).x << endl; + + cout << (p + 1)->x << endl; + cout << (*(p + 1)).x << endl; + + + cout << (*(p+1)).x << endl; + cout << (*(&(*p) + 1)).x << endl; + + + +//地址->变量 + //变量.变量 + + return 0; +} +``` + + + +## 2.函数指针 + +![截屏2020-12-20 下午5.24.14](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-12-20%20%E4%B8%8B%E5%8D%885.24.14.png) + + + +函数指针变量 + +`int (*add)(int, int)` + +函数指针类型 + +`typedef int (*add)(int, int)` + + + +## 3.空指针计算相对地址偏移量 + +> 结构体类型和变量的相对地址偏移量计算 + +```c +#include +#include +#include +#define offset(T, a) ({\ + T temp;\ + (char *)(&temp.a) - (char *)(&temp);\ +}) +//将地址转换成char * 类型方便做计算(因为类型不一样不可以做计算) +#define offset1(T, a) (long)(&(((T *)(NULL))->a)) +//不定义变量,相当于temp不存在(相当于temp),地址从0开始 + +struct Data { + int a; + double b; + char c; +}; + + +int main(int argc, char *argv[], char *env[]) { + + int num = 0x0626364; + //0 62 63 64 + //64 63 62 0/小端机 + printf("%s\n", (char *)(&num)); + //dcb + //98 b,99 c, 100 d + + //结构体类型和变量的相对地址偏移量 + printf("%ld\n", offset(struct Data, a)); + printf("%ld\n", offset(struct Data, b)); + printf("%ld\n", offset(struct Data, c)); + + + return 0; +} +``` + + + + + + + +## 4.typedef用法 + +![截屏2020-12-20 下午5.33.24](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-12-20%20%E4%B8%8B%E5%8D%885.33.24.png) + + + + + +func函数指针类型 + + + + + +## 5.typdef和define别名的区别 + + + +```c +#include +#include +#include +// +#define ppchar char * +typedef char * pchar; + +int main(int argc, char *argv[], char *env[]) { + pchar p1,p2; + ppchar p3,p4; + printf("p1 = %lu, p2 = %lu\n", sizeof(p1), sizeof(p2)); + printf("p3 = %lu, p4 = %lu\n", sizeof(p3), sizeof(p4)); + + return 0; +} +``` + +>p1 = 8, p2 = 8 +>p3 = 8, p4 = 1 +> +>```c +> pchar p1,p2; +> char * p3,p4;==>char * p3;char p4; +>``` +> +>==>define 只是简单是替换 +> +>typedef是重命名 + + + +# 10.main函数 + +## 1.main函数 + +系统调用main函数 + +mian 参数 参数个数,参数,环境变量 + + + +![截屏2020-12-20 下午5.53.10](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-12-20%20%E4%B8%8B%E5%8D%885.53.10.png) + + + + + +## 2.main函数参数 + +> argc:参数个数 +> +> argv:传入的参数以字符串的形式存储 +> +> env:环境变量,存储环境的变量的字符串, +> +> ```c +> ./a.out hello world "hello world" +> agrc = 3 +> argv[0] = ./a.out +> argv[1] = hello +> argv[2] = world +> argv[3] = hello world +> ``` +> +> +> +> + + + +```c +#include +//__attribute__((constructor)) +void output(int argc, char *argv[], char *env[]) { + printf("argc = %d\n", argc); + for (int i = 0; i < argc; i++) { + printf("argv[%d] = %s\n", i, argv[i]); + } + for (int i = 0; env[i]; i++) { + if (!strncmp(env[i], "USER=", 5)) {//判断前5个字符串是不是USER= + if (!strcmp(env[i] + 5, "apricity")) {//对比,相减等于0 + printf("welcome apricity...!\n"); + } else { + printf("you are not the user! please exit\n"); + exit(0); + } + } + printf("env[%d] = %s\n", i, env[i]); + } + +} + +int main(int argc, char *argv[], char *env[]) { + + output(argc, argv, env); + return 0; +} + +./a.out hello world "hello world" +argc = 4 +argv[0] = ./a.out +argv[1] = hello +argv[2] = world +argv[3] = hello world +env[0] = LC_TERMINAL_VERSION=3.4.3 +env[1] = LANG=en_US.UTF-8 +env[2] = LC_TERMINAL=iTerm2 +welcome apricity...! +env[3] = USER=apricity +env[4] = LOGNAME=apricity +env[5] = HOME=/home/apricity +env[6] = PATH=/home/apricity/.autojump/bin:/home/apricity/.autojump/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin +env[7] = MAIL=/var/mail/apricity + +``` + + + + + +# 11.工程项目开发 + + + +正常示例 + +```cpp +#include + +int add(int a, int b) { + return a + b; +} + +void funA(int); +void funB(int); +//两个函数互相调用,需要写申明 +void funB(int n) { + if (n == 0) return ; + printf("funB : %d\n", n); + funA(n - 1); + return ; +} + +void funA(int n) { + if (n == 0) return ; + printf("funA : %d\n", n); + funB(n - 1); + return ; +} + + + +int main() { + add(2, 3); + funA(5); + return 0; +} +``` + + + + + + + +## 11.1 函数未声名和未定义 + + + + + +有了函数申明后可以把函数定义写在函数申明后面,可以快速阅读代码,增加代码阅读性 + + + +源文件.c/cpp + +预编译:宏替换 + +编译期:检查语法错误,生成对象文件,一个(多个)源文件生成一个(多个)对象文件(`g++ -C 40.main.cpp`) + +生成可执行文件(g++ 40.mian.cpp -o main)(-o起别名) + +链接过程报错:编译生成可执行文件,未找到对应的函数,即未定义 + + + +> 函数未申明:编译期报错 +> +> 函数未定义:函数链接过程报错 + + + + + +## 11.2 头文件与源文件 + + + +头文件:后缀名为.h的文件 + +源文件:写代码的文件.c/.cpp/.cc + + + +> 为什么区分头文件源文件? + +头文件放函数声名,避免项目工程中发生函数重定义 + + + + + +## 11.3头文件源文件代码演示 + +```cpp +// 41.main.cpp +#include + +#include "42.40.main.header1.h" +#include "43.40.main.header2.h" + +int main() { + funcA(5); + funcC(5, 6); + return 0; +} + +``` + + + + + +```cpp +// 42.40.main.header1.h +//解决了编译过程中函数重定义的问题 + +#include +void funA(int); +void funcB(int); +void funcA(int n) { + if (n == 0) return ; + printf("funcA :%d\n", n); + funcB(n - 1); + return ; +} + +void funcB(int n) { + if (n == 0) return ; + printf("funcB :%d\n", n); + funcB(n - 1); + return ; +} +``` + + + +```cpp +// 43.40.main.header2.h +void funcC(int, int); +void funcC(int a, int b) { + printf("funcC :%d\n", a + b); + funcA(a); + return ; +} +``` + + + +运行可以通过编译 + +```shell +g++ 40.main.cpp +./a.out +funA : 5 +funB : 4 +funA : 3 +funB : 2 +funA : 1 +``` + + + +修改main.cpp + +```cpp +// 41.main.cpp +#include +#include "43.40.main.header2.h" +#include "42.40.main.header1.h" +//将头文件的顺序 + +int main() { + funcA(5); + funcC(5, 6); + return 0; +} + +// 会报错,funcA函数未声明 +In file included from 41.main.cpp:3: +./43.40.main.header2.h:8:5: error: use of undeclared identifier 'funcA' + funcA(a); + ^ +1 error generated. + + +// 给43的头文件加上声明 +#include +#include "42.41.main.header1.h" +void funA(int); + +void funcC(int, int); +void funcC(int a, int b) { + printf("funcC :%d\n", a + b); + funcA(a); + return ; +} +//报错==>重定义 + In file included from 41.main.cpp:4: +./42.41.main.header1.h:9:6: error: redefinition of 'funcA' +void funcA(int n) { + ^ +./42.41.main.header1.h:9:6: note: previous definition is here +void funcA(int n) { + ^ +In file included from 41.main.cpp:4: +./42.41.main.header1.h:17:6: error: redefinition of 'funcB' +void funcB(int n) { + ^ +./42.41.main.header1.h:17:6: note: previous definition is here +void funcB(int n) { + ^ +2 errors generated. + +//修改main.cpp,去掉42头文件,可以编译通过 +#include +#include "43.40.main.header2.h" +//#include "42.40.main.header1.h" + + +``` + + + +==>另一种修改方法 + +```cpp +#ifndef _HEADER1_H +#define _HEADER1_H +//解决了编译过程中函数重定义的问题 +//判断是否包含当前宏,条件式编译 + +#include +void funA(int); +void funcB(int); +void funcA(int n) { + if (n == 0) return ; + printf("funcA :%d\n", n); + funcB(n - 1); + return ; +} + + +void funcB(int n) { + if (n == 0) return ; + printf("funcB :%d\n", n); + funcB(n - 1); + return ; +} + +#endif +``` + + + + + + + + + +```cpp +#ifndef _HEADER1_H +#define _HEADER1_H +//解决了在一次编译过程中函数重定义的问题 +//判断是否包含当前宏,条件式编译 + +#endif +``` + + + +## 11.4 工程开发示例 + + + +```cpp +//41.main.cpp +#include + +#include "42.41.main.header1.h" +#include "43.41.main.header2.h" +#include "44.41.main.header3.h" + +int main() { + funcA(5); + funcC(5, 6); + funcD(5,6); + return 0; +} +``` + + + +```cpp +//42.41.main.header1.h + +#ifndef _HEADER1_H +#define _HEADER1_H +//解决了编译过程中函数重定义的问题 +//判断是否包含当前宏,条件式编译 + +#include + +void funcA(int); +void funcB(int); + +#endif +``` + + + + + +```cpp +//47.41.main.header1.cc +#include +#include "42.41.main.header1.h" +void funcA(int n) { + if (n == 0) return ; + printf("funcA :%d\n", n); + funcB(n - 1); + return ; +} + +void funcB(int n) { + if (n == 0) return ; + printf("funcB :%d\n", n); + funcB(n - 1); + return ; +} +``` + + + + + +```cpp +//43.41.main.header2.h +#ifndef _HEADER2_H +#define _HEADER2_H + +#include +#include "42.41.main.header1.h" +void funcA(int); +void funcC(int, int); + +#endif +``` + + + +```cpp +//46.41.main.header2.cc + +#include +#include "42.41.main.header1.h" + +void funcC(int a, int b) { + printf("funcC :%d\n", a + b); + funcA(a); + return ; +} +``` + + + + + +```cpp +//44.41.main.header3.h + +#ifndef _HEADER3_H +#define _HEADER3_H + +void funcD(int, int); + +#endif +``` + + + +```cpp +//45.41main.header3.cc + +#include "42.41.main.header1.h" +#include + +void funcD(int a, int b) { + printf("Apricity : %d \n", a + b); + funcA(a); + return ; +} +``` + + + +```shell +g++ -c 47.41.main.header1.cc -o header1.o +g++ -c 46.41.main.header2.cc -o header2.o +g++ -c 46.41.main.header2.cc -o header2.o +g++ -c 45.41main.header3.cc -o header3.o +g++ -c 41.main.cpp -o main.o + +g++ header1.o header2.o header3.o main.o + + +./a.out +funcA :5 +funcB :4 +funcB :3 +funcB :2 +funcB :1 +funcC :11 +funcA :5 +funcB :4 +funcB :3 +funcB :2 +funcB :1 +Apricity : 11 +funcA :5 +funcB :4 +funcB :3 +funcB :2 +funcB :1 +``` + + + +头文件里面放声名,源文件放定义 + + + + + +## 11.5 `<>`和`""`的区别 + + + +`<>`在系统路径下查找头文件 + +`""`从当前目录查找头文件 + + + +如果要对当前目录下的头文件使用`<>`,则在编译的时候使用`g++ -I ./ -c mian.cpp` + +`-I ./`将当前目录添加到系统目录下 + + + + + +## 11.6 makefile + + + +```makefile +.PHONY: clean +CC=g++ -I./ -c +all : 41.main.o 45.41main.header3.o 46.41.main.header2.o 47.41.main.header1.o + g++ 41.main.o 45.41main.header3.o 46.41.main.header2.o 47.41.main.header1.o +41.main.o: 41.main.cpp + ${CC} 41.main.cpp +45.41main.header3.o: 45.41main.header3.cc 44.41.main.header3.h + ${CC} 45.41main.header3.cc +46.41.main.header2.o: 46.41.main.header2.cc 43.41.main.header2.h + ${CC} 46.41.main.header2.cc +47.41.main.header1.o: 47.41.main.header1.cc 42.41.main.header1.h + ${CC} 47.41.main.header1.cc +clean: + rm *.o a.out +``` + + + +## 11.6 动态静态链接库 + + + + + + + + + +src:源文件 + +include:头文件 + + + +```shell +#include : 42.41.main.header1.h 43.41.main.header2.h 44.41.main.header3.h +#src : 45.41main.header3.cc 47.41.main.header1.cc header2.o 46.41.main.header2.cc header1.o header3.o +#lib : + +#打包成静态链接库 +ar -r libhaizei.a *.o +ar: creating archive libhaizei.a + +ls +45.41main.header3.cc 47.41.main.header1.cc header2.o libhaizei.a +46.41.main.header2.cc header1.o header3.o + + + +mv libhaizei.a ../lib +cd ../lib +ll +total 8 +-rw-r--r-- 1 apricity staff 3.0K 2 22 22:07 libhaizei.a + +``` + +使用静态链接库 + + + +```shell +g++ -I ./include -c 41.main.cpp -o main.o +g++ -L ./lib main.o -l haizei +#-L使用静态链接库 -l haizei 引入lib下的haizei库 +``` + + + + + + + +动态链接库和静态连接库的区别: + +动态链接库只需要链接一次,静态每次都需要链接 + + + + + +# 12. C语言测试框架-谷歌测试框架gtest + +下载地址 + +```shell +git clone https://github.com/google/googletest +``` + + + +测试框架: + +## 12.1 测试框架的使用 + +CMake:根据当前环境生成makefile文件 + + + +```shell +cmake CMakeLists.txt +#产生makefile文件 +make +//使用测试框架 +mkdir 43.project +cp -R 42.googletest/lib 43.project +cp -R 42.googletest/googletest/include 43.project + +g++ -I ./include -c main.cpp +g++ -L ./lib/ main.o -lgtest -lpthread +``` + + + +```cpp +//main.cpp +#include +#include + +int add(int a, int b) { + return a + b; +} + +TEST(func, add) { + EXPECT_EQ(add(1, 2), 3); + EXPECT_EQ(add(2, 4), 7); + EXPECT_EQ(add(2, 4), 6); +} + +TEST(func, add2) { + EXPECT_EQ(add(-1, 1), 0); + EXPECT_EQ(add(3, -2), 1); +} + +int main(int argc, char *argv[]) { + testing::InitGoogleTest(&argc,argv); + return RUN_ALL_TESTS(); +} + +``` + + + + + +![截屏2021-02-23 下午5.51.37](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-23%20%E4%B8%8B%E5%8D%885.51.37.png) + + + + + +## 12.2 实现简版gtest + +环境ubutu18.04 + +gcc version 7.5.0 + +include : .h文件 + +src: .cpp源文件 + +目录结构 + +```cpp +43.project + include //gtest include文件夹(.h头文件) + lib // gtest 生成的静态连接库文件 + module //测试框架的实现 + module + bin/haizei //编译后的文件 + include/test.h // 头文件 + main.cpp // 主函数 + makefile // + src/test.cc //源文件 +``` + + + +```cpp +//main.cpp +#include +#include + +int add(int a, int b) { + return a + b; +} + +TEST(func, add) { + EXPECT_EQ(add(1, 2), 3); + EXPECT_EQ(add(2, 4), 7); + EXPECT_EQ(add(2, 4), 6); +} + +TEST(func, add2) { + EXPECT_EQ(add(-1, 1), 0); + EXPECT_EQ(add(3, -2), 1); +} + +TEST(f, uncadd) { + EXPECT_EQ(add(0, 0), 0); + EXPECT_EQ(add(12, -12), 0); +} + +int main(int argc, char *argv[]) { + //testing::InitGoogleTest(&argc,argv); + return RUN_ALL_TESTS(); +} + +``` + + + +`makefile` + +```makefile +.PHONY: clean +all: ./src/test.o main.o + g++ ./src/test.o main.o -o ./bin/haizei +./src/test.o: ./src/test.cc ./include/test.h + g++ -I ./include -c ./src/test.cc -o ./src/test.o +main.o: main.cpp ./include/test.h + g++ -I ./include -c main.cpp +clean: + rm ./bin/haizei ./src/*.o main.o +``` + + + +```cpp +//include/test.h +#ifndef _TEST_H +#define _TEST_H + +#define TEST(a, b)\ +__attribute__((constructor))\ +void a##_haizei_##b()//函数的名字是ab,中间加入符号,解决重名的问题 + +#define EXPECT_EQ(a, b) {\ + printf("%s = %s ? %s \n", #a, #b, a == b ? "TRUE" : "FASE");\ +} + +int RUN_ALL_TESTS(); + + +#endif +``` + + + +```cpp +//src/test.cc + +int RUN_ALL_TESTS() { + for (int i = 0; i < func_cnt; i++) { + func_arr[i].func(); + } + return 0; +} +``` + + + +```cpp +make +./bin/haizei +//运行结果 + +add(1, 2) = 3 ? TRUE +add(2, 4) = 7 ? FASE +add(2, 4) = 6 ? TRUE +add(-1, 1) = 0 ? TRUE +add(3, -2) = 1 ? TRUE +add(0, 0) = 0 ? TRUE +add(12, -12) = 0 ? TRUE +``` + + + + + +## 12.3 最终版getest + +最终运行结果展示 + +![截屏2021-02-25 下午6.12.00](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-25%20%E4%B8%8B%E5%8D%886.12.00.png) + +增加输出颜色 + + + +泛型宏: + +```cpp +_Genericl(a) --> 转换成格式控制字符串 +``` + + + + + +![截屏2021-02-25 下午6.06.38](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-25%20%E4%B8%8B%E5%8D%886.06.38.png) + + + +```c +// ./include/test.h + +#ifndef _TEST_H +#define _TEST_H + +//颜色 +#define COLOR(a, b) "\033[" #b "m" a "\033[0m" //正常颜色 +#define COLOR_HL(a, b) "\033[1;" #b "m" a "\033[0m" //高亮颜色 + +#define GREEN(a) COLOR(a, 32) +#define RED(a) COLOR(a, 31) +#define BLUE(a) COLOR(a, 34) +#define YELLOW(a) COLOR(a, 33) + +#define GREEN_HL(a) COLOR_HL(a, 32) +#define RED_HL(a) COLOR_HL(a, 31) +#define BLUE_HL(a) COLOR_HL(a, 34) +#define YELLOW_HL(a) COLOR_HL(a, 33) + +#define TEST(a, b)\ +void a##_haizei_##b();\ +__attribute__((constructor))\ +void add##_haizei_##a##_haizei_##b() {\ + add_function(a##_haizei_##b, #a "." #b);\ +}\ +void a##_haizei_##b()//函数的名字是ab,中间加入符号,解决重名的问题 + +#define TYPE(a) _Generic((a), \ + int: "%d", \ + double: "%lf", \ + float: "%lf", \ + long long: "%lld", \ + const char*: "%s", \ + char*: "%s") +//TYPE(a)获取a的类型转换成格式占位符 + +#define P(a, color) {\ + char frm[1000];\ + sprintf(frm, color("%s"), TYPE(a));\ + printf(frm, a);\ +} +#define EXPECT(a, b, comp) {\ + __typeof(a) _a = (a);\ + __typeof(b) _b = (b);\ + haizei_test_info.total += 1;\ + if (_a comp _b) haizei_test_info.sucess += 1;\ + else {\ + printf("\n");\ + printf(YELLOW_HL("\t%s:%d: failure\n"), __FILE__, __LINE__);\ + printf(YELLOW_HL("\t\texpect : " #a " " #comp " " #b "\n\t\t" "actual : "));\ + P(_a, YELLOW_HL);\ + printf(YELLOW_HL(" vs "));\ + P(_b, YELLOW_HL)\ + printf("\n\n");\ + }\ + printf(GREEN("[-----------]"));\ + printf(" %s %s %s ? %s \n", #a, #comp, #b, _a comp _b ? GREEN("TRUE") : RED("FASE"));\ +} +// __typeof(a) _a = (a) // 避免add(a++, b) +// 判断ab的关系 +#define EXPECT_EQ(a, b) EXPECT(a, b, ==) // == +#define EXPECT_NE(a, b) EXPECT(a, b, !=) // != +#define EXPECT_LT(a, b) EXPECT(a, b, <) // < +#define EXPECT_LE(a, b) EXPECT(a, b, <=) // <= +#define EXPECT_GT(a, b) EXPECT(a, b, >) // > +#define EXPECT_GE(a, b) EXPECT(a, b, >=) // >= + +typedef void (*TestFuncT)();//函数指针 + +typedef struct Function {//将信息打包成结构体 + TestFuncT func;//指向函数 + const char *str;//名字 +} Function; + +struct FunctionInfo { + int total;//总测试点信息 + int sucess;//成功通过的总数 +}; + +extern struct FunctionInfo haizei_test_info;//用当前结构体类型定义一个变量,extern 申明一个外部变量 +void add_function(TestFuncT, const char *); +int RUN_ALL_TESTS(); + + +#endif + + + +// ./src/test.c +#include +#include +#include +#include + +Function func_arr[100]; +//指针数组 +int func_cnt = 0;//测试用例 +struct FunctionInfo haizei_test_info;//当前变量定义放在源文件里面 + +void add_function(TestFuncT func, const char * str) { + func_arr[func_cnt].func = func; + func_arr[func_cnt].str = strdup(str);//将str拷贝一份,返回新的地址到strl;名字 + func_cnt += 1; + return ; +} + +int RUN_ALL_TESTS() { + for (int i = 0; i < func_cnt; i++) { + printf(GREEN("[====RUN====]") RED_HL(" %s\n"), func_arr[i].str); + haizei_test_info.total = 0; + haizei_test_info.sucess = 0; + func_arr[i].func(); + double rate = 100.0 * haizei_test_info.sucess / haizei_test_info.total; + printf(GREEN("[ ")); + if (fabs(rate - 100.0) < 1e-6) { + printf(BLUE_HL(" %6.2lf%% "), rate); + } else { + printf(RED_HL(" %6.2lf%% "), rate); + } + //printf(GREEN("[-----------]\n")); + printf(GREEN(" ]" " sucess : %d total : %d\n"), + haizei_test_info.sucess, + haizei_test_info.total); + } + return 0; +} + +// ./main.c + +#include +//#include +#include + +int add(int a, int b) { + return a + b; +} + +double double_add(double a, double b) { + return a + b; +} + +TEST(func, add) { + EXPECT_EQ(add(1, 2), 3); + EXPECT_EQ(add(2, 4), 7); + EXPECT_EQ(add(2, 4), 6); + EXPECT_GT(add(0, 9), -9); + EXPECT_GE(add(0, 9), 9); +} + +TEST(func, doubleAdd) { + EXPECT_EQ(double_add(0.5, 2), 2.5); + EXPECT_EQ(double_add(2.1, 0.9), 3); + EXPECT_EQ(double_add(3.2, 3.2), 6); + EXPECT_GT(double_add(0, 3.7), -9); +} + +TEST(func, add2) { + EXPECT_EQ(add(-1, 1), 0); + EXPECT_EQ(add(3, -2), 1); + EXPECT_NE(add(2, 3), 4); + EXPECT_LT(add(-1, -2), 3); + EXPECT_LE(add(0, 6), 5); +} + +TEST(f, uncadd) { + EXPECT_EQ(add(0, 0), 0); + EXPECT_EQ(add(12, -12), 0); +} + +int main(int argc, char *argv[]) { + //testing::InitGoogleTest(&argc,argv); + return RUN_ALL_TESTS(); +} + + +// ./makefile + +.PHONY: clean +all: ./src/test.o main.o + gcc ./src/test.o main.o -o ./bin/haizei +./src/test.o: ./src/test.c ./include/test.h + gcc -I ./include -c ./src/test.c -o ./src/test.o +main.o: main.c ./include/test.h + gcc -I ./include -c main.c +clean: + rm ./bin/haizei ./src/*.o main.o +run: + ./bin/haizei + + +``` + + + +运行 + +```shell +make +gcc -I ./include -c ./src/test.c -o ./src/test.o +gcc -I ./include -c main.c +gcc ./src/test.o main.o -o ./bin/haizei + +make +gcc ./src/test.o main.o -o ./bin/haizei + +make run +./bin/haizei +[====RUN====] func.add +[-----------] add(1, 2) == 3 ? TRUE + + main.c:15: failure + expect : add(2, 4) == 7 + actual : 6 vs 7 + +[-----------] add(2, 4) == 7 ? FASE +[-----------] add(2, 4) == 6 ? TRUE +[-----------] add(0, 9) > -9 ? TRUE +[-----------] add(0, 9) >= 9 ? TRUE +[ 80.00% ] sucess : 4 total : 5 +[====RUN====] func.doubleAdd +[-----------] double_add(0.5, 2) == 2.5 ? TRUE +[-----------] double_add(2.1, 0.9) == 3 ? TRUE + + main.c:24: failure + expect : double_add(3.2, 3.2) == 6 + actual : 6.400000 vs 6 + +[-----------] double_add(3.2, 3.2) == 6 ? FASE +[-----------] double_add(0, 3.7) > -9 ? TRUE +[ 75.00% ] sucess : 3 total : 4 +[====RUN====] func.add2 +[-----------] add(-1, 1) == 0 ? TRUE +[-----------] add(3, -2) == 1 ? TRUE +[-----------] add(2, 3) != 4 ? TRUE +[-----------] add(-1, -2) < 3 ? TRUE + + main.c:33: failure + expect : add(0, 6) <= 5 + actual : 6 vs 5 + +[-----------] add(0, 6) <= 5 ? FASE +[ 80.00% ] sucess : 4 total : 5 +[====RUN====] f.uncadd +[-----------] add(0, 0) == 0 ? TRUE +[-----------] add(12, -12) == 0 ? TRUE +[ 100.00% ] sucess : 2 total : 2 + +make clean +rm ./bin/haizei ./src/*.o main.o +``` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +# 错误总结 + +## 1.`collect2: error: ld returned 1 exit status ` + +```c +/tmp/ccgbUZFw.o: In function `main': +21.my_sqrt.c:(.text+0x12c): undefined reference to `sqrt' +collect2: error: ld returned 1 exit status +``` + + + +编译的时候使用 `gcc a.c -lm` + + + + + +## 2.stack smashing detected ***: terminated + +数组越界 + + + + + + + +## 3.warning: backslash and newline separated by space + + + +将\后面的空格去掉就可以了. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git "a/02.c++\347\254\224\350\256\260/02.Linux\345\237\272\347\241\200.md" "b/02.c++\347\254\224\350\256\260/02.Linux\345\237\272\347\241\200.md" new file mode 100644 index 0000000..6d6612c --- /dev/null +++ "b/02.c++\347\254\224\350\256\260/02.Linux\345\237\272\347\241\200.md" @@ -0,0 +1,3368 @@ + + +# 目录 + + + +[TOC] + +2020.11.18-2020.12.20 + + + + + +# 一、基础知识 + +## 1.`vim`的基本使用 + +> `pwd` (print working directory) +> +> `$0whoamiaptapt ` +> +> `updateapt` +> +> `upgrade` +> `apt-cache search XXX` (搜索XXX软件) +> `apt remove XXX` (卸载XXX软件) +> `dpkg -i xxx.dep` (使用dpkg程序安装xxx.dep)(安装 = install -> -i) +> `dpkg -r` (使用dpkg卸载软件)(卸载 = remove -> -r) + + + + + + + +## 2.`Terminal` + +终端`( terminal )= tty `(Teletypewriter, 电传打印机),作用是提供一个命令的输入输出环 境,在`linux`操作系统下使用组合键 `ctrl + alt + T` 打开的就是终端,可以认为 `terminal` 和 `tty `是 同义词。 + + + +## 3.`shell` + +`shell`是一种人机交互的接口。 `shell` 有壳的意思,是指 “提供使用者使用界面” 的软件,是一种 命令解析器,是`Linux`内核的一个壳,负责外界与Linux内核的交互。 + + + +> `windows` 的cmd就是一种`shell`。所以 `shell` 并不只是指命令语言`-Bash/Zsh`。 其实GUI本身也是一种`shell`. + + + +用户在 `shell` 中提交命令, `shell` 负责接收用户的命令,并扮演命令解析器的⻆色。 + +当你打开一个 `Terminal` 时,操作系统会将 Terminal 和 Shell 关联起来,当我们在 `Terminal` 中输入命令的时候, `Shell` 就负责解释命令。 + + + + + + + + + + + +## 3、命令 + +> 命令是人和计算机交互的基本单位,人使用命令 将要做什么事传达给计算机,计算机做出解 析,并做出回应。 + + + +**命令也有自己的语法结构**: + +和人说话一样,命令的主要结构也可以看做为主谓宾的简单句和复杂句,在命令中,省略主语 + + + + + +> 举个例子 + +`cp(谓语动词) fileA(宾语) fileB(宾语)` + + `cp(谓语动词) -ar(方式状语) fileA(宾语) fileB(宾语)` + +**命令细节** + +> Linux命令由以下几个部分组成: 命令名 , 分隔符 , 选项 和 操作对象 组成。 + +**命令名** + +命令名标识了命令的功能,如cp,rm,mv,rename,fdisk等等,都能很轻易的看出该命令的功能, 还有一些命令,有二级子命令,如apt-get install | remove,install和remove就是apt-get的子命令。 + +**分隔符** + + 分隔符通常为空格,连续的多个空格会被视为一个空格。 + +> 一些特殊的符号也属于分隔符,例如管道`|`,重定向`>,<<,<,>>,`后台运行`&`,序列执行``&&```、;``都 是特殊的分隔符,在使用这些特殊符号的时候,不需要额外加空格,但为了使命令更易读,通常会 加上空格。 + +**选项** + +- 命令的选项通常用 `-` 连接,通常为一个字母,是选项的首字母。大多数命令都可以使用 `-h` 来 查看该命令的帮助。 +- 命令的选项也可以使用 `--` 来引导,接的是选项的全程,效果与—连接单个字母是一样的。 + +**操作对象** + + 操作对象为该命令动作的承受着。 + +**格式约定** + +- 使用 `[]` 来标记可以选择的选项 +- 使用 `|` 来表示不能同时使用的参数 +- 选项通常紧跟命令名,当然,在很多时候我们可以省略命令的选项而使用默认参数 + +## **4.程序与进程** + +> 那么什么是程序呢? +> +> > 计算机程序是指一组指示计算机执行动作或做出判断的指令,通常用某种程序设计语言 +> > 编写,运行于某种目标体系结构上。 + + + +> 什么又是进程呢? +> +> > 进程是程序在内存中的镜像。 + + + +## 5.路径 + +**绝对路径** + +​ 绝对路径的起始点为根目录 `/` ,例如 `/usr/bin/cp` 就是一个绝对路径。 + +**相对路径** + +​ 相对路径的起始点为当前路径 `.` ,假如用户当前目录为 `/home/apricity/` ,那么同样的文件 `cp` ,其 相对路径为 `../../usr/bin/cp` 。 + + + +**远程路径** + +> 在很多时候,我们会需要访问本机之外的资源,这个时候远程路径就有了用武之地了。 + +远程路径的一般表示方法为: `协议://用户名:密码@位置/路径:端口` 比如: http://baidu.com + +​ `ftp://user:passwd@ftp.baidu.com:21` + 远程路径根据应用的不同,具体表示方法和所需要的参数都不太一致,从以上两个远程路径就可以 +看出。 + +**路径相关的命令** + +> `cd` #change directory + +> `ls` #list +> `pwd` #print working directory + +**特殊路径** + +> `~` 家目录 +> `-` 上次工作目录 + + + + + +## **6.软件** + +通常,一个软件包含的内容会分别被拷⻉到同级别的 `bin lib share` 和 `/etc` 目录下。 + +> `bin` 存放程序的可执行文件。在系统环境变量中将该路径添加进去,就可以直接执行程序. +> +> `lib` 库文件集中存放,方便共享。 +> `share` 存放程序需要的其他资源. +> `/etc` 配置文件存放路径,大部分的程序的配置文件都可以在这个路径下找到 + + + + + + + +## 7.**配置方式** + +> `/etc/network/interface` 文件为Ubuntu的网络配置文件,通过修改该文件,并重启网络, 就可以实现网络的配置工作。 + + + + + +直接使用ifconfig命令来直接修改网络配置: + +`ifconfig eth0 172.17.211.175` + +这条命令将把设备` eth0` 的`ip`地址更新为 `172.17.211.175` 。 + +## 8.隐藏文件 + +Linux的隐藏文件都是以` .` 开头的,也就是说所有以 `.` 开头的文件都会被系统识别为隐藏文件。 + +使用` ls` 命令添加 `a` 选项,就可以显示隐藏文件。 这里有两个特殊的目录 `.` 和` ..` ,分别为当前目录和父目录。 + +## 9.文件类型 + +使用 `ll` 命令可以查看当前文件夹下所有文件的详细信息。 + +> `ll` 等效于 `ls -al` + +```bash +apricity@qiu ~ % ll [0] +total 40K +-rw-rw-r-- 1 apricity apricity 0 Nov 22 16:39 aaa.txt +drwxrwxr-x 7 apricity apricity 4.0K Nov 20 19:08 autojump +drwxrwxr-x 2 apricity apricity 4.0K Nov 25 11:34 C语言 +drwxrwxr-x 3 apricity apricity 4.0K Nov 19 14:42 guziqiu +drwxrwxr-x 2 apricity apricity 4.0K Nov 20 19:33 haizei_oj +-rw-rw-r-- 1 apricity apricity 8.3K May 25 2020 install_zsh.sh +drwxrwxr-x 4 apricity apricity 4.0K Nov 22 14:10 linux基础 +drwxrwxr-x 2 apricity apricity 4.0K Nov 22 20:46 test +-rw-rw-r-- 1 apricity apricity 0 Nov 12 01:14 update +drwxrwxr-x 7 apricity apricity 4.0K Nov 12 00:49 zsh-syntax-highlighting +``` + +​ **该命令主要输出了七列内容,分别为:权限、文件数、所属用户、所属群组、文件大小、常⻅日** +**期、文件名。** + **第一列权限,主要可以分为以下四个部分:文件类型,所属用户权限,所属组权限,其他用户权** +**限**。 + + + +> 以刚才执行`ll`的结果中的 `../` 目录的权限为例: +> +> | 文件类型 | 所属用户权限 | 所属群组权限 | 其他用户权限 | +> | :------: | :----------: | :----------: | :----------: | +> | d | rwx | r-x | r-x | + +- 第一部分文件类型为 d ,代表这个文件是一个目录(directory),目录是一种特殊的文件; +- 第二部分所属用户权限为 rwx ,代表该文件拥有者拥有可读(read),可写(write),可执行(execute) 的权限; + +- 第三部分所属群组权限为 r-x ,代表与该文件拥有者在一个群组的用户具有可读,可执行的权 限,在这里` -` 顶替了` w `的位置,代表没有写权限; +- 第四部分其他用户权限同样为可读,可执行。 + +> `rwx` 的顺序是固定的! + + + +Linux中的文件类型除了目录 d 之外,总共有7种文件类型, + +- `-` regular file 普通文件 +- `d` directory目录 +- `l` link链接 +- `b` block 块设备 存储数据以供系统存取的接口设备,也就是硬盘 +- `c` character 字符设备 串口设备,键盘,鼠标等 +- `s` socket套接字`//应用层和底层的接口` +- `p` pipe管道 + + + +对于普通文件又可以分为以下三种: + +- 纯文本文件 + + ​ 纯文本文件使用`ASCII`编码,这是`Linux`系统中最常⻅的一种文件类型,之所以成为纯文本文件,是因 为这种类型的文件是我们可以直接读取的内容,在`Linux`,几乎所有的配置文件都属于这种类型。 + +- 二进制文件 + + ​ 二进制文件是系统中的可执行文件(不包括脚本),计算机只能认识并执行二进制文件。二进制文件不能使用 `cat` 等命令直接读出。 + +- 数据格式的文件 + + 在一些程序运行过程中,需要读取特定格式的文件,这种文件被称为数据文件(data file)。这种文件 通常也不能使用`cat`命令读出。 例如: `/var/log/wtmp` 文件。 + +>使用`ll`命令查看文件,可以看到该文件为普通文件 `ll` == `ls -l` +> +>```bash +> ll /var/log/wtmp +> +> -rw-rw-r-- 1 root utmp 8832 Apr 27 11:50 /var/log/wtmp +>``` +> +>使用file命令查看问价详情,返回为 `data` +> +>```bash +>file /var/log/wtmp +>/var/log/wtmp: data +>``` +> +> + +## 10.文件权限 + +​ + +> 文件的权限包含三组( `u` 用户,` g` 群组,` o` 其他用户),每一组又都包含 三组具体的权限(` r `读,` w `写,` x `执行)。 + +​ 对于文件权限,我们也可以用下面的方式来表示: + +| r | w | x | r | w | x | r | w | x | +| :-----: | :-----: | :-----: | :-----: | :-----: | :-----: | :-----: | :-----: | :-----: | +| (2^2) 4 | (2^1) 2 | (2^0) 1 | (2^2) 4 | (2^1) 2 | (2^0) 1 | (2^2) 4 | (2^1) 2 | (2^0) 1 | + +也就是权限的每一组都由三个十进制的数字表示,该组的的权限就可以简单的用着三个十进制的数 + +字相加得到。 + +> 一个权限为` rwxr-xr-x` 的文件,则它的权限可以使用 `755` 来表示。 + + + + + + + +## 11.**与文件权限有关的命令** + + + + + +```bash +chmod #更改文件权限 +chown #更改文件所属用户 +chgrp #更改文件所属组 +``` + + + +- chmod 的用法 + +```bash +chmod a+x file #给file文件的ugo都赋予执行的权限 +chmod o-x file #将file文件o减去执行权限 +chmod 755 file #设置file文件的权限为rwxr-xr-x +chmod u=rwx,go=rx file #设置file文件的权限为rwxr-xr-x +``` + + + +- chown 的用法 + +```bash +chown guziqiu:apricity file #修改file的所属用户是guziqiu,所属组为apricity + chown -R guziqiu:apricity directory + #修改目录directory及目录下的所有文件的所属用户是guziqiu, 所属组为zpricity + chown guziqiu file #修改file的所属用户为guziqiu +``` + + + +- chgrp 的用法 + +```bash +chgrp root file #修改file所属的组为root +``` + + + +## 12.用户 + +> `Linux`有两类用户,分别是 `root` 和 `普通用户` 。 + +- 超级管理员: `root` + + `root` 拥有系统的完全控制权,所以在使用`Linux`系统的时候,需要慎重使用`root`用户,更多的自由 与权限同样也意味着更大的⻛险。 + +- 普通用户 + + 普通用户拥有的权限就没有 `root` 用户那么大了,它只能做系统允许做的事。普通用户可以执行大 部分的命令,但是`root`专有的命令却不能执行。 + + + +可以使用 su 命令来切换用户: + +```bash +:~$ whoami +guziqiu +:~$ su - root +Password: +:~# whoami +root +:~# su - guziqiu +:~$ whoami +guziqiu +``` + +> `su - guziqiu` +> +> `-`代表一次完整的重新登录 + + + +退出 + +`control +D` + +`exit ` + +`logout` + + + +## 13.其他命令 + + + +`id` + +```bash +apricity@qiu ~ % id [0] +uid=1000(apricity) gid=1000(apricity) groups=1000(apricity),27(sudo) +//用户,组,其他组 +``` + + + + + +1.火车动画 + +​ `sl` + +2.`apt-build moo` + +```bash +apt-build moo + + (__) ~ + (oo) / + _____\/___/ + / /\ / / + ~ / * / + / ___/ +*----/\ + / \ + / / + ~ ~ +..."Have you danced today? Discow!"... +``` + + + +3.cowsay + +用ASCII字符打印牛,羊等动物,还有个cowthink,这个是奶牛想,那个是奶牛说,哈哈,差不多 + +`sudo apt-get install cowsay` + +`cowsay -f tux "坑爹啊"` + +```shell +________ +< 坑爹啊 > + -------- + \ + \ + .--. + |o_o | + |:_/ | + // \ \ + (| | ) + /'\_ _/`\ + \___)=(___/ +``` + + + +4. `cmatrix` 命令 + +这个很酷!《黑客帝国》那种矩阵风格的动画效果 + + `sudo apt-get install cmatrix` + +`cmatrix` + +`q`退出 + + + +# 二、命令系统 + +shell、termial、console + +>shell是一种命令解析器,它给用户提供了一个输入命令并接受机器返回结果的界面。 + +console控制台,一般console只能有一个 + +terminal,它是一个封装程序,一个terminal运行一个shell来扩充为一个具备shell功能的的程序。 + +> console 和 terminal的概念都源自大型机,console可以看作为一个特殊的terminal。现在用的一般都是引申义,一般情况下可以混用。 + + + +命令返回值含义 + +[0]如果为 0,则表示命令执行成功,其它值则表示错误, + +[1]Operation not permitted + +[2]No such file or directory + +```shell +"OS error code 1: Operation not permitted" + "OS error code 2: No such file or directory" + "OS error code 3: No such process" + "OS error code 4: Interrupted system call" + "OS error code 5: Input/output error" + "OS error code 6: No such device or address" + "OS error code 7: Argument list too long" + "OS error code 8: Exec format error" + "OS error code 9: Bad file descriptor" + "OS error code 10: No child processes" + "OS error code 11: Resource temporarily unavailable" + "OS error code 12: Cannot allocate memory" + "OS error code 13: Permission denied" + "OS error code 14: Bad address" + "OS error code 15: Block device required" + "OS error code 16: Device or resource busy" + "OS error code 17: File exists" + "OS error code 18: Invalid cross-device link" + "OS error code 19: No such device" + "OS error code 20: Not a directory" + "OS error code 21: Is a directory" + "OS error code 22: Invalid argument" + "OS error code 23: Too many open files in system" + "OS error code 24: Too many open files" + "OS error code 25: Inappropriate ioctl for device" + "OS error code 26: Text file busy" + "OS error code 27: File too large" + "OS error code 28: No space left on device" + "OS error code 29: Illegal seek" + "OS error code 30: Read-only file system" + "OS error code 31: Too many links" + "OS error code 32: Broken pipe" + "OS error code 33: Numerical argument out of domain" + "OS error code 34: Numerical result out of range" + "OS error code 35: Resource deadlock avoided" + "OS error code 36: File name too long" + "OS error code 37: No locks available" + "OS error code 38: Function not implemented" + "OS error code 39: Directory not empty" + "OS error code 40: Too many levels of symbolic links" + "OS error code 42: No message of desired type" + "OS error code 43: Identifier removed" + "OS error code 44: Channel number out of range" + "OS error code 45: Level 2 not synchronized" + "OS error code 46: Level 3 halted" + "OS error code 47: Level 3 reset" + "OS error code 48: Link number out of range" + "OS error code 49: Protocol driver not attached" + "OS error code 50: No CSI structure available" + "OS error code 51: Level 2 halted" + "OS error code 52: Invalid exchange" + "OS error code 53: Invalid request descriptor" + "OS error code 54: Exchange full" + "OS error code 55: No anode" + "OS error code 56: Invalid request code" + "OS error code 57: Invalid slot" + "OS error code 59: Bad font file format" + "OS error code 60: Device not a stream" + "OS error code 61: No data available" + "OS error code 62: Timer expired" + "OS error code 63: Out of streams resources" + "OS error code 64: Machine is not on the network" + "OS error code 65: Package not installed" + "OS error code 66: Object is remote" + "OS error code 67: Link has been severed" + "OS error code 68: Advertise error" + "OS error code 69: Srmount error" + "OS error code 70: Communication error on send" + "OS error code 71: Protocol error" + "OS error code 72: Multihop attempted" + "OS error code 73: RFS specific error" + "OS error code 74: Bad message" + "OS error code 75: Value too large for defined data type" + "OS error code 76: Name not unique on network" + "OS error code 77: File descriptor in bad state" + "OS error code 78: Remote address changed" + "OS error code 79: Can not access a needed shared library" + "OS error code 80: Accessing a corrupted shared library" + "OS error code 81: .lib section in a.out corrupted" + "OS error code 82: Attempting to link in too many shared libraries" + "OS error code 83: Cannot exec a shared library directly" + "OS error code 84: Invalid or incomplete multibyte or wide character" + "OS error code 85: Interrupted system call should be restarted" + "OS error code 86: Streams pipe error" + "OS error code 87: Too many users" + "OS error code 88: Socket operation on non-socket" + "OS error code 89: Destination address required" + "OS error code 90: Message too long" + "OS error code 91: Protocol wrong type for socket" + "OS error code 92: Protocol not available" + "OS error code 93: Protocol not supported" + "OS error code 94: Socket type not supported" + "OS error code 95: Operation not supported" + "OS error code 96: Protocol family not supported" + "OS error code 97: Address family not supported by protocol" + "OS error code 98: Address already in use" + "OS error code 99: Cannot assign requested address" + "OS error code 100: Network is down" + "OS error code 101: Network is unreachable" + "OS error code 102: Network dropped connection on reset" + "OS error code 103: Software caused connection abort" + "OS error code 104: Connection reset by peer" + "OS error code 105: No buffer space available" + "OS error code 106: Transport endpoint is already connected" + "OS error code 107: Transport endpoint is not connected" + "OS error code 108: Cannot send after transport endpoint shutdown" + "OS error code 109: Too many references: cannot splice" + "OS error code 110: Connection timed out" + "OS error code 111: Connection refused" + "OS error code 112: Host is down" + "OS error code 113: No route to host" + "OS error code 114: Operation already in progress" + "OS error code 115: Operation now in progress" + "OS error code 116: Stale NFS file handle" + "OS error code 117: Structure needs cleaning" + "OS error code 118: Not a XENIX named type file" + "OS error code 119: No XENIX semaphores available" + "OS error code 120: Is a named type file" + "OS error code 121: Remote I/O error" + "OS error code 122: Disk quota exceeded" + "OS error code 123: No medium found" + "OS error code 124: Wrong medium type" + "OS error code 125: Operation canceled" + "OS error code 126: Required key not available" + "OS error code 127: Key has expired" + "OS error code 128: Key has been revoked" + "OS error code 129: Key was rejected by service" + "OS error code 130: Owner died" + "OS error code 131: State not recoverable" +``` + + + +## 1.Linux帮助系统 + +`man`手册 + +`man ls` + +`ls(1)ls`第一部分 + +`user commands` 所属部分:用户命令 + +`info`手册 + +`ls --time =atime -l` + +`man scanf` + +`linux porgrammer's manual`:所属部分:系统开发者 + + + +> 在学习使用Linux的时候,我们会遇到很多以前没有用过的命令和功能,这个时候最好的解决办法就是求助于**man**. + +`man`的使用方法很简单,例如查看`ls`的帮助手册,可以直接使用命令`man ls`即可查看`ls`的命令帮助。 + +```sh +LS(1) User Commands LS(1) + + + +NAME #命令全名,简单的说明及用法 + ls - list directory contents + +SYNOPSIS #基本语法 + ls [OPTION]... [FILE]... + +DESCRIPTION #详细说明语法中参数的用法 + List information about the FILEs (the current directory by default). Sort entries alphabeti‐ + cally if none of -cftuvSUX nor --sort is specified. + + Mandatory arguments to long options are mandatory for short options too. + + -a, --all + do not ignore entries starting with . + + -A, --almost-all + do not list implied . and .. + + --author + with -l, print the author of each file + #中间省略 + -1 list one file per line + + --help display this help and exit + + --version + output version information and exit + + SIZE may be (or may be an integer optionally followed by) one of following: KB 1000, K 1024, + MB 1000*1000, M 1024*1024, and so on for G, T, P, E, Z, Y. + + Using color to distinguish file types is disabled both by default and with --color=never. + With --color=auto, ls emits color codes only when standard output is connected to a terminal. + The LS_COLORS environment variable can change the settings. Use the dircolors command to set + it. + + Exit status: #错误返回值 + 0 if OK, + + 1 if minor problems (e.g., cannot access subdirectory), + + 2 if serious trouble (e.g., cannot access command-line argument). + +AUTHOR #作者 + Written by Richard M. Stallman and David MacKenzie. + +REPORTING BUGS #bug提交联系方式 + Report ls bugs to bug-coreutils@gnu.org + GNU coreutils home page: + General help using GNU software: + Report ls translation bugs to + +COPYRIGHT #版权保护 + Copyright © 2011 Free Software Foundation, Inc. License GPLv3+: GNU GPL version 3 or later + . + This is free software: you are free to change and redistribute it. There is NO WARRANTY, to + the extent permitted by law. + +SEE ALSO ##在哪里可以看到更多关于该命令的文档 + The full documentation for ls is maintained as a Texinfo manual. If the info and ls programs + are properly installed at your site, the command + + info coreutils 'ls invocation' + + should give you access to the complete manual. + + + +GNU coreutils 8.12.197-032bb September 2011 LS(1) +``` + +> 眼尖的同学应该注意到了手册中开头和结尾的地方写的`LS(1)`了,那么这是什么意思呢?它代表的是一般用户可使用的命令。 + +在man中,常见的几个数字的含义如下表所示: + +| 代号 | 代表的含义 | 举例 | +| :--: | :------------------------------------------ | ------------------ | +| 1 | 用户在shell环境下可以操作的命令或可执行文件 | `man 1 ls` | +| 2 | 系统内核可调用的函数和工具 | `man 2 reboot` | +| 3 | 一些常用的函数与函数库,大部分C的函数库 | `man 3 readdir` | +| 4 | 设备文件的说明,通常是在`/dev`下的设备 | `man 4 null` | +| 5 | 配置文件或某些文件的格式 | `man 5 interfaces` | +| 6 | 游戏 | `man 6 lol` 😂 | +| 7 | 惯例与协议等,例如Linux文件系统,网络协议等 | `man 7 tcp` | +| 8 | 系统管理员可用的命令 | `man 8 reboot` | +| 9 | 跟kernel有关的文件 | | +| o | 旧文档 | | +| n | 新文档 | | +| l | 本地文档 | | + + + +在man手册中,我们可以用到的快捷键如下: + +| 快捷键 | 功能 | 快捷键 | 功能 | +| --------------- | ---------- | ----------- | ---------- | +| Ctrl+f(orward) | 向下翻一页 | Ctrl+d(own) | 向下翻半页 | +| Ctrl+b(ackward) | 向上翻一页 | Ctrl+u(p) | 向上翻半页 | +| / | 查找 | q(uit) | 退出 | + + + +随堂练习: + +```shell +man -f #whatis +man -k #apropos +``` + +> 在Linux中还有一种在线求助的方式`info`,有兴趣的可以了解一下。 + + + + + + + +## 2.linux常用命令 + +1.文件及目录相关,文件内容的修改与查看 + +| | | +| --------------------- | ----------------------------------- | +| `ls`查看目录下内容 | `cd`目录跳转 | +| `pwd`打印工作目录 | `cp`拷贝 | +| `mv`移动文件及目录 | `rm`删除文件及目录 | +| `mkdir`创建目录 | `tree`打印目录树 | +| `tar`文件归档与压缩 | `ln`创建链接文件 | +| `touch`创建空白文件 | `head`查看文件头部 | +| `cat`查看文件内容 | tail查看文件尾部 | +| `vim`文本编辑器 | `diff`对比文件 | +| `echo`打印文本 | `grep`检索信息 | +| `more`分页查看文件 | `wc`计数 | +| `less`分页查看文件 | `find`查找文件 | +| `which`查找可执行文件 | `whereis`查找可执行、源码、帮助手册 | +| `locate`定位任何文件 | | + + + +2.用户相关命令,进程相关命令 + +| | | +| --------------------- | ------------------------ | +| `useradd`新建用户 | `chgrp`修改所属组 | +| `userdel`删除用户 | `chmod`修改文件权限 | +| `usermod`修改用户 | `chown`修改文件所属用户 | +| `passwd`修改密码 | `logout`退出用户 | +| `su`切换用户 | `exit`退出用户 | +| `sudo`获取管理员权限 | | +| `ps`打印进程 | `ctrl+z`挂起前台任务 | +| `kill`杀死进程 | `fg`将进程调至前台运行 | +| `pkill`批量杀死进程 | `bg`让挂起的进程后台执行 | +| `killall`批量杀死进程 | `jobs`查看挂起和后台进程 | +| `crotab`定时任务 | | + +3.系统信息命令获取 + +| `date` 查看时间 | `dstat`查看系统信息 | +| -------------------- | -------------------- | +| `df`查看文件系统 | `nmon`查看系统信息 | +| `du`获取目录文件大小 | `ifconfig`查看IP信息 | +| `free`查看内存 | `uname`查看OS信息 | +| `top`查看系统信息 | `last`查看最近登录 | +| `htop`查看系统信息 | `who`查看当前登录 | + + + +4.其他命令 + +| | | +| -------------------- | -------------- | +| `ssh`远程连接 | `poweroff`关机 | +| `scp`远程拷贝 | `reboot`重启 | +| `wget`获取`http`文件 | | +| `ping`测试远程主机 | | + + + + + + + + + +# 三、zsh + +### 1.通配符 + +* ? + + 代表**单个任意字符** + +* * + + 代表**任意几个任意字符** + +> 请看下面的例子,并自己动手尝试一下通配符的使用 + +```bash +suyelu@HaiZei-Tech:~$ ls +a.log code HaiZei helloworld 你好world +suyelu@HaiZei-Tech:~$ ls *ld +helloworld 你好world +suyelu@HaiZei-Tech:~$ ls ??world +你好world +suyelu@HaiZei-Tech:~$ ls ?????world +helloworld +suyelu@HaiZei-Tech:~$ +``` + +除了*****和**?**这两个通配符之外,Linux中还有下面几种通配符 + +| 通配符 | 含义 | 举例 | +| --------------------- | ----------------------------------------- | ------------------------------------------------------------ | +| [list] | 匹配list中的任意单一字符 | a[xyz]b a与b之间有且只有一个字符, 且只能是x或y或z, 如: axb, ayb, azb。 | +| [!list] | 匹配除list中的任意单一字符 | a[!0-9]b a与b之间有且只有一个字符, 但不能是数字, 如axb, aab, a-b等 | +| [c1-c2] | 匹配c1-c2中的任意单一字符 | a[0-9]b a与b之间有且只有一个字符,该字符是0-9之间的数字,如a0b, a1b,... ,a9b。 | +| {string1,string2,...} | 匹配 sring1 或 string2 (或更多)其一字符串 | a{abc,xyz,123}b a与b之间只能是abc或xyz或123这三个字符串之一。 | + + + + + +### 2.任务管理 + + + + + +`PATH=${PATH}:.` + +locate + +1.`&` + +在命令的后面加上**`&`**表示后台执行的意思 + +```shell +command & +``` + + + +2.`;` + +在命令之间以**`;`**相连,表示顺序执行的意思 + +```shell +command1;command2 +``` + +3.**`&&`** + +命令之间以**`&&`**相连时,只有第一个命令成功执行,第二个命令才会执行 + +```shell +command1 && command2 +``` + + + +4.**`||`** + +命令之间以**`||`**相连时,如果前一个命令执行失败,则执行第二个命令 + +```shell +command1 || command2 +``` + +5.**` `` `** + +命令中如果包含另一个命令,则用符号**``** 将它包括起来,在执行的时候系统将优先执行**``**中的子命令,然后将其结果代入父命令继续执行 + +```shell +command1 `command2` +``` + +6.**`ctrl + z`** + +在shell中执行命令时,同时按下**`ctrl + z`**可以将暂时挂起 + + +```shell +suyelu@HaiZei-Tech:~$ vim helloworld.c + +[1]+ Stopped vim helloworld.c +suyelu@HaiZei-Tech:~$ +``` + + >什么是挂起? + + +7. **`bg`** + + 执行**`bg`**命令,可以将挂起的命令后台运行 + +```shell +suyelu@HaiZei-Tech:~$ vim helloworld.c + + +[1]+ Stopped vim helloworld.c +suyelu@HaiZei-Tech:~$ bg +[1]+ vim helloworld.c & +suyelu@HaiZei-Tech:~$ +``` + + + +8. **`fg`** + + 执行命令**`fg`**可以将后台执行的命令转为前台执行 + +9. **`jobs`** + + 在Linux系统中,执行**`jobs`**命令可以查看所有在后台执行和挂起的任务以及任务编号 + +```shell +suyelu@HaiZei-Tech:~/code$ jobs +[1]- Stopped vim a.c (wd: ~) +[2]+ Stopped vim b.c (wd: ~) +[3] Running ./a.out & +``` + +> 尝试执行**fg**和**bg**加上任务编号,看看是什么效果 + + + + + +%没有输入换行符zsh输出一个%座位标记 + + + +/tmp零时文件夹 + + + +BIOS:开机检测 + +> 检测硬件环境 +> +> Boot Loader读内核文件 +> +> /sbin/init 启动进程 +> +> 加载磁盘 + + + + + + + +guziqiu: x : + +x代表密码占位符 + +### 3.管道、重定向 + +1. `>` + + 重定向符 + +2. `>>` + + 作用于**>**基本相同,不同的是**>>**将内容追加到文件的末尾,而**>**内容覆盖原文件 + +3. `<` + + 与**>**刚好相反,是从文件到命令的重定向。它将文件的内容输出到命令作为输入 + +4. `<<` 标记文件结尾 + + + +``` +cat >> /etc/sysconfig/network << EOF +HOSTNAME=$HOST_NAME +EOF +``` + +### 4.转义符 + +> 在Linux中转义符**`\`**的应用十分广泛,除此之外,转义符还包括**“”**和**‘’**。 + +| 字符 | 说明 | +| ---- | ------------------------------------------------------------ | +| '' | 硬转义,硬引用,其内部所有的shell元字符、通配符都会被关掉。注意,硬转义中不允许出现’(单引号)。 | +| "" | 软转义,软引用,其内部只允许出现特定的shell元字符($,\`,\\):$用于变量值替换、`用于命令替换、\用于转义单个字符 | +| \ | 反斜杠,转义,去除其后紧跟的元字符或通配符的特殊意义。 | + + + +下表是部分转义字符对应表: + +| 转义字符 | 意义 | ASCII码值(十进制) | +| -------- | ----------------------------------- | ------------------- | +| \a | 响铃(BEL) | 007 | +| \b | 退格(BS) ,将当前位置移到前一列 | 008 | +| \f | 换页(FF),将当前位置移到下页开头 | 012 | +| \n | 换行(LF) ,将当前位置移到下一行开头 | 010 | +| \r | 回车(CR) ,将当前位置移到本行开头 | 013 | +| \t | 水平制表(HT) (跳到下一个TAB位置) | 009 | +| \v | 垂直制表(VT) | 011 | +| \\\ | 代表一个反斜线字符''\' | 092 | +| \' | 代表一个单引号(撇号)字符 | 039 | +| \" | 代表一个双引号字符 | 034 | +| \? | 代表一个问号 | 063 | +| \0 | 空字符(NULL) | 000 | +| \nnn | 1到3位八进制数所代表的任意字符 | 三位八进制 | +| \xnnn | 1到3位十六进制所代表的任意字符 | 三位十六进制 | + +#### 转义符在Shell中的应用 + +```shell +suyelu@HaiZei-Tech:~$ echo -e "\044" +$ +suyelu@HaiZei-Tech:~$ echo "\044" +\044 +suyelu@HaiZei-Tech:~$ echo $'\044' +$ +suyelu@HaiZei-Tech:~$ echo $'\44' +$ +``` + + + + + +## 附录1 Shell元字符 + +> + +| 字符 | 说明 | +| ---- | ------------------------------------------------------------ | +| = | 变量名=值,为变量赋值。注意=左右紧跟变量名和值,中间不要有空格 | +| `` | 取命令的执行结果,与下文的$有相似之处 | +| $ | 变量值替换,$变量名替换为shell变量的值;为避免在文本连接时混淆,请使用${变量名};$0...$9 代表shell文件的参数。**$()**同``;**${}**限定变量名的范围;**$[]**整数计算; | +| > | prog > file 将标准输出重定向到文件。 | +| >> | prog >> file 将标准输出追加到文件。 | +| < | prog < file 从文件file中获取标准输入 | +| << | | +| \| | 管道命令,例:p1 \| p2 将p1的标准输出作为p2的标准输入 | +| & | 后台运行命令,最大好处是无需等待命令执行结束,就可以在同一命令行下继续输入命令 | +| () | 在子shell中执行命令,在子进程中执行 | +| {} | 在当前shell中执行命令,或用在变量替换的界定范围(例如上面的${变量名}用法)。 | +| [] | 字符通配,匹配括号内之一 | +| ; | 命令结束符。例如p1;p2表示先执行p1,再执行p2 | +| && | 前一个命令执行成功后,才继续执行下一个命令。例:p1 && p2 ;若p1执行成功后,才执行p2,反之,不执行p2; | +| \|\| | 前一个命令执行失败后,才继续执行下一个命令。例:p1 \|\| p2 ;若p1执行成功后,不执行p2,反之,才执行p2; | +| ! | 执行历史记录中的命令**!731**;匹配最近的一次命令**!echo**;取非**ls /dev/sda[!1]**;结果取反**! echo ok ;echo $?** | +| % | **% 1**相当于**fg 1** | +| ^ | 取非,和**!**雷同;**`^string1^string2^`**将上一命令的string1替换为string2 | +| ~ | home目录 | +| # | 注释 | +| * | 通配符,任意字符 | +| ? | 通配符,任一字符 | +| . | 当前目录;source | +| - | 减号;上次工作目录 | +| : | 真值;空命令 | +| \ | 转义 | +| / | 目录分割符 | + + + + + +## 附录2 ASCII码 对照表 + +| Bin(二进制) | Oct(八进制) | Dec(十进制) | Hex(十六进制) | 缩写/字符 | 解释 | +| ----------- | ----------- | ----------- | ------------- | --------------------------- | ------------ | +| 0000 0000 | 0 | 0 | 00 | NUL(null) | 空字符 | +| 0000 0001 | 1 | 1 | 01 | SOH(start of headline) | 标题开始 | +| 0000 0010 | 2 | 2 | 02 | STX (start of text) | 正文开始 | +| 0000 0011 | 3 | 3 | 03 | ETX (end of text) | 正文结束 | +| 0000 0100 | 4 | 4 | 04 | EOT (end of transmission) | 传输结束 | +| 0000 0101 | 5 | 5 | 05 | ENQ (enquiry) | 请求 | +| 0000 0110 | 6 | 6 | 06 | ACK (acknowledge) | 收到通知 | +| 0000 0111 | 7 | 7 | 07 | BEL (bell) | 响铃 | +| 0000 1000 | 10 | 8 | 08 | BS (backspace) | 退格 | +| 0000 1001 | 11 | 9 | 09 | HT (horizontal tab) | 水平制表符 | +| 0000 1010 | 12 | 10 | 0A | LF (NL line feed, new line) | 换行键 | +| 0000 1011 | 13 | 11 | 0B | VT (vertical tab) | 垂直制表符 | +| 0000 1100 | 14 | 12 | 0C | FF (NP form feed, new page) | 换页键 | +| 0000 1101 | 15 | 13 | 0D | CR (carriage return) | 回车键 | +| 0000 1110 | 16 | 14 | 0E | SO (shift out) | 不用切换 | +| 0000 1111 | 17 | 15 | 0F | SI (shift in) | 启用切换 | +| 0001 0000 | 20 | 16 | 10 | DLE (data link escape) | 数据链路转义 | +| 0001 0001 | 21 | 17 | 11 | DC1 (device control 1) | 设备控制1 | +| 0001 0010 | 22 | 18 | 12 | DC2 (device control 2) | 设备控制2 | +| 0001 0011 | 23 | 19 | 13 | DC3 (device control 3) | 设备控制3 | +| 0001 0100 | 24 | 20 | 14 | DC4 (device control 4) | 设备控制4 | +| 0001 0101 | 25 | 21 | 15 | NAK (negative acknowledge) | 拒绝接收 | +| 0001 0110 | 26 | 22 | 16 | SYN (synchronous idle) | 同步空闲 | +| 0001 0111 | 27 | 23 | 17 | ETB (end of trans. block) | 结束传输块 | +| 0001 1000 | 30 | 24 | 18 | CAN (cancel) | 取消 | +| 0001 1001 | 31 | 25 | 19 | EM (end of medium) | 媒介结束 | +| 0001 1010 | 32 | 26 | 1A | SUB (substitute) | 代替 | +| 0001 1011 | 33 | 27 | 1B | ESC (escape) | 换码(溢出) | +| 0001 1100 | 34 | 28 | 1C | FS (file separator) | 文件分隔符 | +| 0001 1101 | 35 | 29 | 1D | GS (group separator) | 分组符 | +| 0001 1110 | 36 | 30 | 1E | RS (record separator) | 记录分隔符 | +| 0001 1111 | 37 | 31 | 1F | US (unit separator) | 单元分隔符 | +| 0010 0000 | 40 | 32 | 20 | (space) | 空格 | +| 0010 0001 | 41 | 33 | 21 | ! | 叹号 | +| 0010 0010 | 42 | 34 | 22 | " | 双引号 | +| 0010 0011 | 43 | 35 | 23 | # | 井号 | +| 0010 0100 | 44 | 36 | 24 | $ | 美元符 | +| 0010 0101 | 45 | 37 | 25 | % | 百分号 | +| 0010 0110 | 46 | 38 | 26 | & | 和号 | +| 0010 0111 | 47 | 39 | 27 | ' | 闭单引号 | +| 0010 1000 | 50 | 40 | 28 | ( | 开括号 | +| 0010 1001 | 51 | 41 | 29 | ) | 闭括号 | +| 0010 1010 | 52 | 42 | 2A | * | 星号 | +| 0010 1011 | 53 | 43 | 2B | + | 加号 | +| 0010 1100 | 54 | 44 | 2C | , | 逗号 | +| 0010 1101 | 55 | 45 | 2D | - | 减号/破折号 | +| 0010 1110 | 56 | 46 | 2E | . | 句号 | +| 00101111 | 57 | 47 | 2F | / | 斜杠 | +| 00110000 | 60 | 48 | 30 | 0 | 数字0 | +| 00110001 | 61 | 49 | 31 | 1 | 数字1 | +| 00110010 | 62 | 50 | 32 | 2 | 数字2 | +| 00110011 | 63 | 51 | 33 | 3 | 数字3 | +| 00110100 | 64 | 52 | 34 | 4 | 数字4 | +| 00110101 | 65 | 53 | 35 | 5 | 数字5 | +| 00110110 | 66 | 54 | 36 | 6 | 数字6 | +| 00110111 | 67 | 55 | 37 | 7 | 数字7 | +| 00111000 | 70 | 56 | 38 | 8 | 数字8 | +| 00111001 | 71 | 57 | 39 | 9 | 数字9 | +| 00111010 | 72 | 58 | 3A | : | 冒号 | +| 00111011 | 73 | 59 | 3B | ; | 分号 | +| 00111100 | 74 | 60 | 3C | < | 小于 | +| 00111101 | 75 | 61 | 3D | = | 等号 | +| 00111110 | 76 | 62 | 3E | > | 大于 | +| 00111111 | 77 | 63 | 3F | ? | 问号 | +| 01000000 | 100 | 64 | 40 | @ | 电子邮件符号 | +| 01000001 | 101 | 65 | 41 | A | 大写字母A | +| 01000010 | 102 | 66 | 42 | B | 大写字母B | +| 01000011 | 103 | 67 | 43 | C | 大写字母C | +| 01000100 | 104 | 68 | 44 | D | 大写字母D | +| 01000101 | 105 | 69 | 45 | E | 大写字母E | +| 01000110 | 106 | 70 | 46 | F | 大写字母F | +| 01000111 | 107 | 71 | 47 | G | 大写字母G | +| 01001000 | 110 | 72 | 48 | H | 大写字母H | +| 01001001 | 111 | 73 | 49 | I | 大写字母I | +| 01001010 | 112 | 74 | 4A | J | 大写字母J | +| 01001011 | 113 | 75 | 4B | K | 大写字母K | +| 01001100 | 114 | 76 | 4C | L | 大写字母L | +| 01001101 | 115 | 77 | 4D | M | 大写字母M | +| 01001110 | 116 | 78 | 4E | N | 大写字母N | +| 01001111 | 117 | 79 | 4F | O | 大写字母O | +| 01010000 | 120 | 80 | 50 | P | 大写字母P | +| 01010001 | 121 | 81 | 51 | Q | 大写字母Q | +| 01010010 | 122 | 82 | 52 | R | 大写字母R | +| 01010011 | 123 | 83 | 53 | S | 大写字母S | +| 01010100 | 124 | 84 | 54 | T | 大写字母T | +| 01010101 | 125 | 85 | 55 | U | 大写字母U | +| 01010110 | 126 | 86 | 56 | V | 大写字母V | +| 01010111 | 127 | 87 | 57 | W | 大写字母W | +| 01011000 | 130 | 88 | 58 | X | 大写字母X | +| 01011001 | 131 | 89 | 59 | Y | 大写字母Y | +| 01011010 | 132 | 90 | 5A | Z | 大写字母Z | +| 01011011 | 133 | 91 | 5B | [ | 开方括号 | +| 01011100 | 134 | 92 | 5C | \ | 反斜杠 | +| 01011101 | 135 | 93 | 5D | ] | 闭方括号 | +| 01011110 | 136 | 94 | 5E | ^ | 脱字符 | +| 01011111 | 137 | 95 | 5F | _ | 下划线 | +| 01100000 | 140 | 96 | 60 | ` | 开单引号 | +| 01100001 | 141 | 97 | 61 | a | 小写字母a | +| 01100010 | 142 | 98 | 62 | b | 小写字母b | +| 01100011 | 143 | 99 | 63 | c | 小写字母c | +| 01100100 | 144 | 100 | 64 | d | 小写字母d | +| 01100101 | 145 | 101 | 65 | e | 小写字母e | +| 01100110 | 146 | 102 | 66 | f | 小写字母f | +| 01100111 | 147 | 103 | 67 | g | 小写字母g | +| 01101000 | 150 | 104 | 68 | h | 小写字母h | +| 01101001 | 151 | 105 | 69 | i | 小写字母i | +| 01101010 | 152 | 106 | 6A | j | 小写字母j | +| 01101011 | 153 | 107 | 6B | k | 小写字母k | +| 01101100 | 154 | 108 | 6C | l | 小写字母l | +| 01101101 | 155 | 109 | 6D | m | 小写字母m | +| 01101110 | 156 | 110 | 6E | n | 小写字母n | +| 01101111 | 157 | 111 | 6F | o | 小写字母o | +| 01110000 | 160 | 112 | 70 | p | 小写字母p | +| 01110001 | 161 | 113 | 71 | q | 小写字母q | +| 01110010 | 162 | 114 | 72 | r | 小写字母r | +| 01110011 | 163 | 115 | 73 | s | 小写字母s | +| 01110100 | 164 | 116 | 74 | t | 小写字母t | +| 01110101 | 165 | 117 | 75 | u | 小写字母u | +| 01110110 | 166 | 118 | 76 | v | 小写字母v | +| 01110111 | 167 | 119 | 77 | w | 小写字母w | +| 01111000 | 170 | 120 | 78 | x | 小写字母x | +| 01111001 | 171 | 121 | 79 | y | 小写字母y | +| 01111010 | 172 | 122 | 7A | z | 小写字母z | +| 01111011 | 173 | 123 | 7B | { | 开花括号 | +| 01111100 | 174 | 124 | 7C | \| | 垂线 | +| 01111101 | 175 | 125 | 7D | } | 闭花括号 | +| 01111110 | 176 | 126 | 7E | ~ | 波浪号 | +| 01111111 | 177 | 127 | 7F | DEL (delete) | 删除 | + + + + + + + +# 四、linux系统信息获取 + + + + + +| 命令 | 功能 | +| -------- | -------------------------------- | +| `update` | 打印系统运行时长和平均负载 | +| `who` | 显示当前系统登录的用户信息 | +| `last` | 显示用户最近登录信息 | +| `date` | 显示或设置系统时间与日期 | +| `w` | 当前登录用户列表及正在执行的任务 | +| `whoami` | 打印当前有效的用户名称 | +| `uname` | 打印当前系统信息 | +| `cal` | 显示日历 | + +`uptime`系统运行时长 + +```shell +apricity@qiu ~ % uptime + 14:28:15 up 6 days, 22:24, 1 user, load average: 1.88, 1.19, 1.01 +``` + + + +`w`获取当前用户和正在执行的进程 + +```shell +apricity@qiu ~ % w [127] + 14:33:51 up 6 days, 22:29, 1 user, load average: 0.96, 0.95, 0.95 +USER TTY FROM LOGIN@ IDLE JCPU PCPU WHAT +apricity pts/0 112.54.13.167 14:00 1.00s 3.35s 0.00s w +``` + + + +`tty`虚拟终端 `from` 从哪里登录,`loging`在什么时候登录 + + + + + +` last | grep -v "wtmp begins" | grep -v "^$" | grep -v "reboot" |ƒ |sort | uniq -c | sort -n -r | head -n 2` + + + +```shell +#上一次登录停留了多长时间 +``` + + + + + + + +`uname` + +```shell +apricity@qiu linux基础 % uname -a +Linux qiu 4.15.0-117-generic #118-Ubuntu SMP Fri Sep 4 20:02:41 UTC 2020 x86_64 x86_64 x86_64 GNU/Linux + + +apricity@qiu linux % date +"%Y-%m-%d %H:%M:%S" [0] +2020-11-29 15:40:28 +``` + + + + + +cal日历 + + + + + + + +# 五、shell编程 + + + +```shell +#!/bin/bash +#这是注释 +# !不是注释,指定解释器,固定格式 +echo 'hello' + + +:< 变量的定义 +> +> ​ a=12 +> +> 局部变量 +> +> ​ local a=12 +> +> 特殊变量 +> +> - 位置变量 +> +> $0:获取当前执行shell脚本文件名,包括路径 +> +> $*:获取当前shell的所有参数 +> +> $#:得到当前脚本的参数个数 +> +> $@:获取这个程序所有的参数 + + + +状态变量 + +​ $?:判断上一个指令是否执行成功,0为成功,非零不成功 + +​ $$:获取当前进程的PID + +​ $!:上一个指令的PID + + + + + + + +## 2.输入输出 + +`echo` + + + +`echo -e "hello\n"#开启转义` + +`printf` + + + +```shell +#软转义 +apricity@qiu 1.shell % a="first.sh:${a}" [0] +apricity@qiu 1.shell % echo $a [0] +first.sh:first.sh +#硬转义 +apricity@qiu 1.shell % a='first.sh:${a}' [0] +apricity@qiu 1.shell % echo $a [0] +first.sh:${a} +``` + + + +```shell +echo -e “\033[32m 绿色字 \033[0m” +``` + +```shell + +#命令替换符``,替换命令 +apricity@qiu 1.shell % a=`pwd` [0] +apricity@qiu 1.shell % pwd [0] +/home/apricity/linux/1.shell +apricity@qiu 1.shell % echo $a [0] +/home/apricity/linux/1.shell + + +``` + + + + + +```shell + +apricity@qiu 1.shell % PATH=${PATH}:first.sh [0] +apricity@qiu 1.shell % echo $PATH [0] +/home/apricity/.autojump/bin:/home/apricity/.autojump/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin:first.sh + + +``` + + + + + +特殊变量 + + + +`echo $?`上一条命令的执行结果 + +read + +​ -s静默模式输入密码 + +​ + +## 3.函数 + +```bash +#!/bin/bash +function __printf__() { + echo "$1" + return +} + + +__printf__ "hello guziqiu" +``` + + + + + +## 4.流程控制语句 + +### 1.`if` + +```bash +#!/bin/bash +read a +if [[ $a -gt 10 ]];then + echo "$a > 10" +else + echo "$a <= 10" +fi + + + +#!/bin/bash +read a +if [[ ${a} -gt 10 ]];then + echo "$a > 10" +elif [[ ${a} -eq 10 ]];then + echo "$a = 10" +else + echo "$a < 10" +fi +``` + + + +`test`表达式 + + + +### 2.`while` + +```shell +while [[ condition ]]; do +done +``` + + + + + +```bash +#!/bin/bash + + +num=0 +while [[ $num -lt 100 ]];do + echo ${num} + num=$[ ${num} + 1 ] +done +``` + + + +### 3.case + + + +```shell +#!/bin/bash +case words in + pattern) + ;; +esac +``` + + + +```bash +#!/bin/bash +read a +case $a in + 1) + echo 1 + ;; + 2) + echo 2 + ;; +esac + + + +read a +case $a in + 1) + echo 1 + ;; + 2) + echo 2 + ;; + default) + echo "not found" + ;; +esac +``` + + + + + +遍历当前目录所有文件 + +```bash +for i in `ls *.sh`;do [0] +echo $i +done +1.first.sh +2.func.sh +3.if.sh +4.case.sh +``` + + + + + + + +### 4.for + +```shell +for in in words; do +done +for (( i=0; i<10; i++ )); do +done +``` + + + +```bash +#!/bin/bash +for i in $@;do + echo $i +done + + +for ((i=1; i <= 100; i++));do + echo $i +done +``` + + + + + + + +### 5.until + +```shell +#!/bin/bash +until [[ condition ]];do +done +``` + + + +```bash +#!/bin/bash +num=0 +until [[ $num -eq 100 ]];do + echo $num + num=$[ ${num} + 1 ] +done +``` + + + + + + + +100以内偶数求和 + +```bash +#!/bin/bash +sum=0 +for (( i=1; i<=100; i++ ));do + if [[ $[ ${i} % 2 ] -eq 0 ]];then + sum=$[ ${sum} + ${i} ] + fi +done +echo ${sum} +``` + + + +空格敏感, + +=号左右不能加空格 + + + + + +$a变量名 + +$[ 整数计算 ] + +[[ test表达式 ]] + + + +## 5.数组 + +`declare -a a` + +>输出数组内容 +> +>`${array[*]}` +> +>`${array[@]}` + + + +> 确定数组元素个数 +> +> `${#array[@]}` + + + +> 找到数组的下标 +> +> `${!array[@]}` + + + +> 追加数组 +> +> `array+=(a,b,c);` + +> 数组排序 +> +> `sort` + +> 删除数组元素 +> +> `unset` + + + + + + + + + +```bash +apricity@qiu:~/linux/1.shell$ read -a a +1 2 3 4 5 6 7 + + +apricity@qiu:~/linux/1.shell$ echo ${a} +1 +apricity@qiu:~/linux/1.shell$ echo ${a[1]} +2 + + +apricity@qiu:~/linux/1.shell$ echo ${a[@]} +1 2 3 4 5 6 7 + +apricity@qiu:~/linux/1.shell$ echo ${a[*]} +1 2 3 4 5 6 7 + +``` + + + + + +```shell + +#查看数组下标 +apricity@qiu:~/linux/1.shell$ echo ${!c[*]} +0 1 2 +apricity@qiu:~/linux/1.shell$ echo ${!c[@]} +0 1 2 + +apricity@qiu:~/linux/1.shell$ c+=(4 5 6) +apricity@qiu:~/linux/1.shell$ echo ${c[@]} +0 1 3 4 5 6 +apricity@qiu:~/linux/1.shell$ echo ${c[@]} | sort +0 1 3 4 5 6 + + +apricity@qiu:~/linux/1.shell$ unset c +apricity@qiu:~/linux/1.shell$ echo ${c[@]} + +``` + + + + + +```shell +apricity@qiu:~/linux/1.shell$ d=(1 2 3 4 5 6 7 8) +apricity@qiu:~/linux/1.shell$ echo ${d[*]} +1 2 3 4 5 6 7 8 + +``` + + + +```shell +apricity@qiu:~/linux/1.shell$ echo ${a[*]} +1 2 3 4 5 6 7 +apricity@qiu:~/linux/1.shell$ echo ${a[1]} +2 +apricity@qiu:~/linux/1.shell$ unset a[1] +apricity@qiu:~/linux/1.shell$ echo ${a[1]} + +apricity@qiu:~/linux/1.shell$ echo ${a[*]} +1 3 4 5 6 7 + +apricity@qiu:~/linux/1.shell$ echo ${!a[*]} +0 2 3 4 5 6 +``` + + + + + + + +素数筛 + + + +```shell + +#!/bin/bash +if [[ $# -ne 2 ]];then + echo "error" + exit +fi + +start=$1 +end=$2 + +declare -a prime + +function init_prime() { + local end=$1 + local i + prime[1]=1 + for (( i=2; i <= ${end}; i++ ));do + for (( j=$[ 2 * ${i} ]; j <= ${end}; j+=i ));do + prime[${j}]=1 + done + done +} + +init_prime ${end} + + +for (( i=${start}; i<=${end}; i++ ));do + if [[ ${prime[$i]}x == x ]];then + sum=$[ ${sum} + ${i} ] + fi +done + +echo ${sum} +``` + + + + + + + +# 六、文件及目录 + + + +### 1.目录 + +`cd`切换到当前目录 + +`cd ..`切换到上层目录 + +`cd.` 切换到当前目录 + +`cd~/cd` 回到自己家目录 + +`cd -`回到 上次工作目录 + +`cd ~guziqiu/`切换到guziqiu的目录 + + + + + +`pwd` 打印当前工作目录 + + + +```shell +ls -ald run/ [0] +drwxr-xr-x 21 root root 660 Dec 2 18:14 run/ +``` + + + + + +`pwd -L`显示逻辑工作目录 + +`pwd -P`显示物理工作目录 + +(链接文件) + +```shell +apricity@qiu run % pwd [0] +/var/run +apricity@qiu run % pwd -L [0] +/var/run +apricity@qiu run % pwd -P [0] +/run +``` + + + + + +```shell +ln ./1.shell/test/ . [0] +ln: ./1.shell/test/: hard link not allowed for directory +#hard link硬链接 +``` + + + +`mkdir -p`自动创建父目录 + + + + + +!加一个命令匹配命令或者history里面的行数会输出显示该命令 + + + +`./a.out` + +`./`是当前目录的意思 + + + + + +### 2.文件及目录管理 + + + +`cp [irapdslu]` + +> ➢选项 +> ➢`-i`: 若文件存在,询问用户 +> ➢`-r`:递归复制 +> `-a`:pdr的集合 +> ➢`-p`:连同文件属性一起拷贝 +> ➢`-d`: 若源文件为连接文件的属性,则复制连接文件的属性 +> ➢`-s`:拷贝为软连接 +> ➢`-1`:拷贝为硬连接 +> ➢`-u`:源文件比目的文件新才拷贝 +> ➢尝试: `cp filel file2 . dir` + + + +`rm` + +> `rm [irf]`` +> +> ``-i`:互动模式 +> +> `-r`:递归删除 +> +> `-f`:force +> +> + + + +`mv` + +> `mv [ifu]` +> +> `-i`:互动模式 +> +> `-u`: 源文件更新才会移动 +> +> `-f`:force + + + +### 3.文件内容查阅 + +`cat`正向连续读 + +> ➢`cat [-AbEnTv] ` +> ➢选项: +> ➢`-A`:相当于-vET +> ➢-`V`:列出看不出的字符 +> `-E`:显示断行符为`$` +> ➢`-T`:显示TAB为`^I` +> ➢`-b`:列出行号 +> ➢`-n`:列出行号,连同空行也编号 + + + + + + + +`tac`反向连续读 + +`nl`输出行号显示文件 + +>➢`nl [-bnw] ` +>➢选项 +>➢`-b`:行号指定的方式 +> `-b a`:相当于cat -n +> `-b t`:相当于cat -b +>➢`-n`:列出行号的表示方法 +> `-n ln`:行号在屏幕最左边显示 +> `-n rn`:行号在自己字段的最右边显示 +> `-n rz`:行号在自己字段的最右边显示,前面自动补全0 +>➢`-w `:行号所占位数 + + + + + + + +`more`一页一页的显示内容 + + + +>MORE按页查看 +>`more file` +>➢`/string`向下查找string关键字 +>➢`:f`显示文件名称和当前显示的行数 +>➢`q`离开 + + + + + + + +`less`与相似但是可以上下翻看 + +>LESS按页查看 +> +>➢`less file` +>➢`/string`向下查找 `n`:继续向下查找 +>➢`/?string`反向查找`N`:继续反向查询 + + + + + + + +`head`只看头几行 + +> HEAD查看头几行 +> ➢`head [-n num] ` +> ➢`-nnum`:显示前num行 +> ➢`-n -num`:除了后num行外,其他的都显示 + + + + + +`tail`只看尾几行 + +>TAIL查看末尾几行 +>➢`tail [-n num] ` +>➢`-n num`:显示文件后num行 +>➢`-n +num`:除了前num行,其他的都显示 + + + + + +`od`以二进制形式查看文件内容 + +>OD二_进制文件查看 +>➢`od [-t TYPE] ` +>➢`-t` : +> ➢`a`:默认字符输出 +> ➢`c`:ASCII字符输出 +> ➢`d[size]`:十进制输出,每个数占用size bytes +> ➢`f[size]`:浮点数输出... +> ➢`o[size]`:八进制输出.... +> ➢`x[size]:`十六进制输出... + + + +more和all + + + + + +### 4.修改文件时间与新建文件 + +> ➢文件的三个时间 +> ➢`mtime`:内容数据改动时才更新这个时间; +> ➢`ctime`:文件的权限,属性改动时更新这个时间 +> ➢`atime`:文件的内容被取用access时,更新这个时间 +> ➢`ls -1 --time=ctime /etc/hostname` + + + + + + + +> ➢`touch [ - -acdmt] ` +> ➢`-a`:仅修改访问时间 +> ➢`-c`:仅修改文件的时间,若文件不存在,不新建 +> ➢`-d`:修改文件日期 +> `-m`:仅修改mtime +> ➢`-t`:修改文件时间[ yymmddhhmm] + +### 5.文件隐藏属性 + +> ➢`chattr [+-=][ASacdistu] ` +> ➢`A`:不修改atime +> ➢`S`:同步写入 +> ➢`a`:只能增加数据 +> ➢`c`:自动压缩,解压 +> ➢`d`:不会被dump程序备份 +> ➢`i`:不能删除,修改,建立连接 +> ➢`s`:文件删除时,直接从磁盘删除 +> ➢`u`:文件删除时,数据内容存在磁盘中 + + + + + +> ➢`lsattr [-adR] ` +> ➢`-a`:打印隐藏文件的隐藏属性 +> ➢`-d`:如果是目录,仅打印目录的信息 +> ➢`-R`:递归 + + + + + +### 6.文件的特殊权限 + + + +`set_iuid` + + + + +| 权限 | | 作用对象 | 效果 | +| ------------ | ---- | ---------------------- | -------------------------------------- | +| `set_uid` | S | 二进制程序文件,非脚本 | 用户在执行改程序时获得程序所有者权限 | +| `set_gid` | s | 目录和二进制文件 | 用户在该目录里,有效组变为目录所属组, | +| `sticky bit` | t | 目录 | 在该目录下,用户只能删除自己创建的内容 | + +`chmod +t file` + + + +### 7.命令与文件的查询 + + + +`whereis`寻找特定文件 + +>➢`whereis [ - .bmsu] ` +>➢`-b`:只查找二进制文件 +>➢`-m`: 只查找manual路径下的文件 +>➢`-s`:只查找source源文件 +>`-u`:查找其他文件 + +`which`寻找执行文件,查找path路径下所有可执行文件 + + + +`locate` 搜索文件(可部分查找) + +>`LOCATE`模糊定位 +>➢`locate [-ir] keyword` +>➢`-i`:忽略大小写 +>➢`-r`:后面可接正则表达式 +>➢相关文件: `/ect/updatedb . conf` +>➢`/var/lib/mlocate` + + + +`find`多样化高级查找 + +>`FIND`高级査找 +> +>find 目录 -name 目录名 +> +>➢`find [PATH] [ option] [action ]` +>➢与用户或用户组相关的参数: +>➢`-uid n`:用户UID为n +>➢`-gid n`:群组Gid为n +>➢`-user name`: 用户名为name +>➢`-group name`:群组名为name +>➢`-nouser`:文件所有者不存在 +>➢`nogroup`:文件所在组不存在 +> +> +> +>➢与文件权限及名称有关的参数: +>➢`-name filename`: 文件名为filename +>➢`-size [+-] SIZE`:查找比SIZE大或小的 +>➢`-type TYPE`: `f b c d l s p` +>➢`-perm mode`: mode刚好等于的文件 +>➢-`perm -mode`:全部包含mode的文件 +>➢`find -exec ls -l {} \;` +> +>-exec 命令的开始 +> +>\命令结尾 +> +> +> +>〉`find [PATH] [option] [ action]` +>与时间相关的参数: -`atime, -ctime, -mtime` +>`-mtime n`: n天前的“一天之内’修改的文件amin +>〉`-mtime +n`: n天之前,不包含n,修改过的文件 +>〉`-mtime - -n`: n天之内,包含n,修改过的文件 +>》`-newer file`: 比file还要新的文件 + + + + + +```shell + ls -al `which passwd` [10] +-rwsr-xr-x 1 root root 59640 Mar 23 2019 /usr/bin/passwd +``` + + + + + + + +## linux三剑客awk + +awk文本数据处理 + +`awk [-Ffv] 'BEGIN{ commands} pattern { commands} END { commands}' file` + +```shell +last | grep -v "^$" | grep -v "wtmp" | grep -v "still"| head -5 | awk 'BEGIN {print "start\n"} {if ($1 == "apricity") {print $10}} END {printf("%s\n","Bye")}'| grep -v "^$" |cut -c 2-6 |awk -F: -v hour=0 -v min=0 '{hour+=$1;min+=$2} END {if (min>=60) {hour+=min/60;min=min%60} printf("%d hours, %d min",hour,min)}' +3 hours, 2 min% +``` + + + + + + + + + + + +# 七、数据提取操作 + + + + + + + +| 命令 | 说明 | 命令 | 说明 | +| ------- | ---------------- | ------- | -------------------- | +| `cut` | 切分 | `grep` | 检索 | +| `sort` | 排序 | `wc` | 统计字符、字数、行数 | +| `uniq` | 去重 | `tee` | 双向重导项 | +| `split` | 文件切分 | `xargs` | `参数代换` | +| `tr` | 替换、压缩和删除 | | | + + + +## 1.cut 切分 + +>`➢cut [-dfc] ` +> +>​ `-d c`:以c字符分割(c在字符中) +> +>​ `-f num`: 显示num字段的内容`[n- ; n-m;-m]` +>​ `-b num`: 字节 +>​ `-c num`: 字符 + +`cut -d : -f 1` + +## 2.grep检索 + +>`grep [-acinv] ` +> `-a`:将二进制文件以普通文件的形式搜寻数据 +> `-c`:统计搜寻到的次数 +> +>​ `-C`:统计 +> +>​ `-i`:忽略大小写 +>​ `-n`:顺序输出行号 +>​ `-v`:反向输出(输出没找到的) + +`grep -v "^$"` 过滤空白符 + +​ + + + +`ps -ef` linux下任务管理器 + + + + + + + + + +## 3.sort排序 + + + +>➢`sort [ - fbMnrtuk] ` +>➢`-f`:忽略大小写 +>➢`-b`:忽略最前面的空格符 +>➢`-M`:以月份名称排序 +>➢`-n`:以纯数字方式排序 +> ` -r`:反向排序 +>➢`-u`:uniq +>➢`-t`:分割符,默认[TAB ] +>➢`-k`:以那个区间排序 + + + +## 4.WC统计字符,字数,行数 + +>`wc [ - lwm] ` +>➢`-1`:仅列出行号 +>➢`-w`:仅列出多少字 +>➢`-m`:仅列出多少字符 + + + +## 5.UNIQ去重 + +> ➢`uniq [-ic]` +> ➢`-i`:忽略大小写字符的不同 +> ➢`-c`:进行计数 + + + + + +## 6.TEE 双向重导项 + +> `tee [-a] file` +> `-a`:append + + + +## 7.SPLIT文件切分 + +> ➢`split [-bl] PREFIX` +> ➢`-b SIZE`: 切分为SIZE大小的文件 +> ➢`-l num`: 以num行为大小切分 + +```shell +ls /etc/ | split -l 10 + ls [0] +xaa xab xac xad xae xaf xag xah xai xaj xak xal xam xan xao xap xaq xar xas +``` + + + +## 8.xargs参数代换 + +> ➢`xargs [ -0pne] ` +> ➢`-0`:将特殊字符还原为普通字符 +> ➢`-eEOF`: 当xargs读到EOF时停止 +> ➢`-p`:执行指令前询问 +> ➢`-n num`: 每次执行command时需要的参数个数 + +```zsh +echo "/etc/" | xargs ls + + +echo " . /bin /etc" | xargs -e"/bin" ls [0] +xaa xab xac xad xae xaf xag xah xai xaj xak xal xam xan xao xap xaq xar xas + + +cat /etc/passwd | cut -d : -f 1 |xargs -p -n 1 id + +``` + + + + + +## 9.tr 对标准输入的字符替换,压缩,删除 + +> ➢`tr [cdst] <第一字符集> <第二字符集>` +> ➢`c` 用字符集二取代所有不属于第一字符集的字符 +> ➢`d`删除所有属于第一字符集的字符 +> ➢`s`将连续重复的字符以单独一个字符表示 +> ➢`t`先删除第一字符集较第二字符集多出的字符 + +` man ls | tr -sc "a-zA-Z" " "` + +`man ls | tr -sc "a-zA-Z" "\n" | sort | uniq -c | sort -nr | head -9` + + + +## 10.数据提取练习 + + + +> 1.tr命令对文件的重命名,内容的替换操作 + + + +> #“1 2 3 4 5 6 7 9 a v 你好 . /8” +> #求以上字符串中所有数字之和 + +`echo "1 2 3 4 5 6 7 9 a v 你好 . /8" | tr -c "1-9" "\n" | grep -v "^$" | awk '{sum+=$1} END {print sum}'` +`45` + +> #echo “ABCefg” >> test.log +> +> #请将该文件中所有大写字母转换为小写 + + + `echo ABCefg | tr 'A-Z' 'a-z'` + + + +> 2.找到`PATH`变量中的最后一个路径。 + + + +`echo ${PATH} | tr ":" "\n" | tail -1` + + + +> 3.使用`last`命令,输出所有的关机信息。 + + + +`ast | grep "reboot"` + + + +> 4.将`/etc/passwd`中的内容按照用户名排序。 + +`cat /etc/passwd | sort -t : -k 1` + + + +> 5.将`/etc/passwd`中的内容按`uid`排序。 + +`cat /etc/passwd | sort -t : -k 3` + + + +> 6.在云主机上查找系统登录用户的总人次。 + +`last | grep -v "^$" | grep -v "wtmp" | grep -v "reboot" | wc -l` + + + +> 7.将云主机中登录的所有用户名,按照次数排序,并输出次数。 + +`last | grep -v "^$" | grep -v "wtmp" | grep -v "reboot" | cut -d " " -f 1 | sort | uniq -c` + 105 apricity + 10 guziqiu + 9 root + 3 test + +> 8.将本地的`/etc`目录下的文件及目录,每十条保存到一个文件中。 + +`ls /etc/ | split -l 10` + +> 9.将`/etc/passwd`中存放的第10到20个用户,输出`uid`,`gid`和`groups`。 + +`cat /etc/passwd| head -n 20 | tail -n 10 | cut -d : -f 1 | xargs -n 1 id` + +> 10.将按照用户名查看`/etc/passwd`中的用户,读到`'sync'`用户结束。 + +​ `cat /etc/passwd | cut -d : -f 1 | xargs -e"sync"` + + + +> 11.词频统计 + +```bash +使用下面这个命令生成一个文本文件。 +cat >> a.txt << xxx +nihao hello hello 你好 +nihao +hello + +ls + +cd +world +pwd +xxx +``` + +统计a.txt中各词的词频,并按照从大到小的顺序输出。 + +`cat a.txt | tr -s " " "\n" | sort | uniq -c | sort -n -r | awk '{print $2, $1}'` + +`cat a.txt | tr " " "\n" | grep -v "^$" | sort | uniq -c | sort -r` + + + + + + + + + +# 八、软/硬链接 + + + +ext4文件系统将系统划分为inode、block、spuerblock() + +## 1.block + +>文件是存储在硬盘上的,硬盘的最小存储单位叫做扇区`sector`,每个扇区存储`512字节`。操作系统读取硬盘的时候,不会一个个扇区地读取,这样效率太低,而是一次性连续读取多个扇区,即一次性读取一个块`block`。这种由多个扇区组成的块,是文件存取的最小单位。块的大小,最常见的是`4KB`,即连续八个`sector`组成一个`block`。 + + + +## 2.inode文件结点 + +> 文件数据存储在块中,那么还必须找到一个地方存储文件的元信息,比如文件的创建者、文件的创建日期、文件的大小等等。这种存储文件元信息的区域就叫做`inode`,中文译名为`索引节点`,也叫`i节点`。因此,一个文件必须占用一个`inode`,但至少占用一个`block`。 + +- 元信息 → inode +- 数据 → block + +------ + + + +>`inode`包含很多的文件元信息,但不包含文件名,例如:字节数、属主`UserID`、属组`GroupID`、读写执行权限、时间戳等。 +> +>而文件名存放在目录当中,但`Linux`系统内部不使用文件名,而是使用`inode号码`识别文件。对于系统来说文件名只是`inode号码`便于识别的别称。 + + + + + +## 3.硬链接 + +> 通过文件系统的`inode`链接来产生的新的文件名,而不是产生新的文件,称为硬链接。 +> +> 一般情况下,每个`inode`号码对应一个文件名,但是`Linux`允许多个文件名指向同一个`inode`号码。意味着可以使用不同的文件名访问相同的内容。 + + + +`ln 源文件 目标` + + + +> 运行该命令以后,源文件与目标文件的`inode`号码相同,都指向同一个`inode`。`inode`信息中的链接数这时就会增加`1`。 + +> 当一个文件拥有多个硬链接时,对文件内容修改,会影响到所有文件名;但是删除一个文件名,不影响另一个文件名的访问。删除一个文件名,只会使得`inode`中的链接数减`1`。 + + + +> 需要注意的是不能对目录做硬链接。 +> +> 通过`mkdir`命令创建一个新目录,其硬链接数应该有`2`个,因为常见的目录本身为`1`个硬链接,而目录下面的隐藏目录`.(点号)`是该目录的又一个硬链接,也算是`1`个连接数。 +> +> 直观理解是,如果允许硬链接指向目录,假设目录.../d1/...与.../d2/...互为硬链接,那么在d1下必然包含目录项“..”,试问这个“..”应该指向d1还是d2? + + + + + +## 4.软连接 + +类似于Windows的快捷方式功能的文件,可以快速连接到目标文件或目录,称为软链接。 + +`ln -s 源文件或目录 目标文件或目录` + +>软链接就是再创建一个独立的文件,而这个文件会让数据的读取指向它连接的那个文件的文件名。 +> +>例如,文件`A`和文件`B`的`inode`号码虽然不一样,但是文件`A`的内容是文件`B`的路径。读取文件`A`时,系统会自动将访问者导向文件`B`。这时,文件`A`就称为文件`B`的软链接`soft link`或者符号链接`symbolic link`。 + + + +>这意味着,文件`A`依赖于文件`B`而存在,如果删除了文件`B`,打开文件`A`就会报错。 +> +>这是软链接与硬链接最大的不同: +> +>文件`A`指向文件`B`的文件名,而不是文件`B`的`inode`号码, +> +>文件`B`的`inode`链接数不会因此发生变化。 + + + + + + + +# 九、shell实现线性筛 + + + +```shell +#!/bin/bash + + +if [[ $# -ne 2 ]];then + echo "Usage: $0 start end" + exit +fi + +start=$1 +end=$2 + +declare -a prime +sum=0 +for (( i=2; i<=${end}; i++ ));do + if [[ ${prime[$i]}x == x ]];then + prime[0]=$[ ${prime[0]} + 1 ] + prime[${prime[0]}]=${i} + fi + for (( j=1; j<=${prime[0]}; j++ ));do + if [[ $[ $i * ${prime[$j]} ] -gt ${end} ]];then + break; + fi + + done +done +for (( i=1; i<=${prime[0]}; i++ ));do + sum=$[ ${sum} + ${prime[$i]} ] +done +echo $sum +``` + + + + + + + +# 十、sed + + + + + + + + + +1.替换文件每行中第一个出现的正则表达式,并打印结果: +`sed 's/{{regex}}/{{replace}}/' {{filename}}` + +2.***Replace all occurrences of an extended regular expression in a file, and print the result: +`sed -r 's/{{regex}}/{{replace}}/g' {{filename}}` + +`/g`全局替换 + +```zsh +at a.log | sed 's/abc/123/' +123123 abc +frrrrrrtyd +axcvrewgfdddddds + +123 + + cat a.log | sed -r 's/abc/123/g' +123123 123 +frrrrrrtyd +axcvrewgfdddddds + +123 +``` + + + +3.Replace all occurrences of a string in a file, overwriting the file (i.e. in-place): +`sed -i 's/{{find}}/{{replace}}/g' {{filename}}` + + + +4.- Replace only on lines matching the line pattern: + `sed '/{{line_pattern}}/s/{{find}}/{{replace}}/' {{filename}}` + +```shell +sed '/1/s/abc/123/' a.log +123123 abc +frrrrrrtyd +axcvrewgfdddddds + + + +abc + +cat a.log +abc123 abc +frrrrrrtyd +axcvrewgfdddddds + + + +abc +``` + + + +替换常用 + + + +5. ***Delete lines matching the line pattern: + `sed '/{{line_pattern}}/d' {{filename}}` + + + +```shell +#删除5-7行 +apricity@Apricity test % cat a.log | sed "5,7d" +abc123 abc +frrrrrrtyd +axcvrewgfdddddds + +abc + + +apricity@Apricity test % cat a.log +abc123 abc +frrrrrrtyd +axcvrewgfdddddds + +#script +echo "abc" +#END +abc + +#只删一行 +apricity@Apricity test % cat a.log | sed "/script/d" [0] +abc123 abc +frrrrrrtyd +axcvrewgfdddddds + +echo "abc" +#END +abc +``` + + + +6.***Replace separator / by any other character not used in the find or replace patterns, e.g., #: + +`sed 's#{{find}}#{{replace}}#' {{filename}}` + +可以用`#.,`代替`/` + +7.Print the first 11 lines of a file: +`sed 11q {{filename}}` + + + + + + + +![截屏2020-12-08 上午11.53.06](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-12-08%20%E4%B8%8A%E5%8D%8811.53.06.png) + +将123全部替换为abc + +8.将行首替换为`$` + +```shell +sed 's/^/\$/g' a.log +$abc123 abc +$frrrrrrtyd +$axcvrewgfdddddds +$ +$#script +$echo "abc" +$#END +$abc +``` + + + + + +9.找到//里面的内容并且删除直到行尾 + +```shell + cat a.log | sed '/script/,$d' +abc123 abc +frrrrrrtyd +axcvrewgfdddddds + +``` + + + + + + + + + +# 总结: + +## 1.vim + +快速跳转: + +> 行首 0 +> +> 行尾 $ +> +> 文件开始 gg +> +> 文件末尾 GG +> +> 任意行 :12,12G + + + +复制 + +> 拷贝 yy +> +> 剪切 dd +> +> 多行操作 3yy/dd + + + + + +删除 + +> x 删除 +> +> 2dd +> +> d2G 删除两行 +> +> + +vimtuter帮助手册 + + + + + + + +linux + +> linux历史 +> +> +> +> linux本质:linux内核操作系统,其他功能是各开发商开发 +> +> +> +> unix:云计算后开始减少 + + + + + + + + + +## 2.shell + +zsh + +> zsh相关文件 +> +> > zshenv:一般放环境变量 +> > +> > zprofile:用于用户login时读入(执行)的配置 +> > +> > zshrc:用户开启交互是命令行时读入的配置 +> +> +> +> 环境变量概念: +> +> 环境变量是在操作系统中一个具有特定名字的对象,它包含了一个或者多个应用程序所将使用到的信息。 +> +> 执行命令的本质:从文件系统调用执行文件,返回结果 + + + +![截屏2020-12-15 上午11.47.50](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-12-15%20%E4%B8%8A%E5%8D%8811.47.50.png) + + + + + +僵尸进程,父亲生了孩子,孩子没有被回收 + + + +man手册 + +> man +> +> tldr +> +> man -k +> +> 检索 前后移动 +> +> Baidu +> +> 查找命令 +> +> tab 自动补全命令 +> +> man -k +> +> - man -k:根据关键字搜索联机帮助,是一种模糊搜索。例如要查找"passwd"相关的信息,使用man -k passwd会找到很多和passwd相关的帮助页。 +> - man -f:关键字精确搜索,与-k不同,它只搜索与关键字完全匹配的帮助页 + + + + + + + + + +## 3.linux具体使用 + +文件及目录 + +远程拷贝ssh 、sshfs、scp + +sshfs:远程挂载 + + + +文件查阅 [查阅](#3.文件内容查阅) + +> `cat` +> +> `head` +> +> - -n<行数> 显示的行数。 +> +> `tail` +> +> - -n<行数> 显示文件的尾部 n 行内容 +> +> `tail -f` +> +> - -f 循环读取 +> +> - ``` +> tail -f notes.log +> ``` +> +> 此命令显示 notes.log 文件的最后 10 行。当将某些行添加至 notes.log 文件时,tail 命令会继续显示这些行。 显示一直继续,直到您按下(Ctrl-C)组合键停止显示。 +> +> `more` +> +> `less`可回看、检索信息高亮显示 + + + +移动和复制 + +> `cp` +> +> `mv` +> +> `ln`硬链接,软链接 +> +> `rm` + + + + + +文件类型及权限 + +> 七种: +> +> 普通文件 +> +> 目录 +> +> 字符 +> +> 块 +> +> 管道 +> +> 链接 +> +> socket + + + +权限 `rwx` + +>目录`x`可以`cd`进去 +> +>用户分组 `ugo` +> +>命令 +> +>`chmod、chgrp、chown、usermod` +> +>特殊权限: +> +>`set uid` +> +>只能改自己的权限 +> +>`set gid` 用户临时进入目录时,创建一个文件时,该用户会临时变成目录所属组 +> +>`sed uid` 当用户执行文件时,用户临时拥有此文件,如密码文件 +> +>`sbit`黏着位 目录下的文件只能被所属者删掉 +> +>隐藏属性 + + + + + + + + + + + +4.文件时间 + +> `atime` 访问时间,跟新过于频繁也不好,耗时 +> +> `ctime` 修改时间 +> +> `mtime` 修正时间 ls默认 +> +> `touch` 创建文件,修改时间 + + + + + +文件定位: + +> 特殊文件: +> +> `which` +> +> `whereis` +> +> 普通文件 +> +> `locate` db updatedb(基于数据库) +> +> `find` + + + + + +数据处理[数据提取操作](#七、数据提取操作) + +> cut +> +> tr +> +> sort +> +> uniq +> +> grep +> +> awk +> +> sed + + + + + + + +重定向 + +> \> +> +> />> +> +> < +> +> << +> +> /\`a\` + + + +读入数据:read + +```shell +vim 1.sh +read a +read b +echo$[ $a + $b ] + +vim 1.in +2 +3 + +bash 1.sh < 1.in +5 +``` + + + +```shell +vim 2.sh +str='' +read str +echo ${str} +vim 2.in +guziqiu is 18years old. + +bash 2.sh < 2.in +``` + + + + + + + +进程管理 + +> `fg` +> +> `bg` +> +> `jobs` + + + + + +定时任务 + +> crontab -e + + + +系统信息获取 + +> `unname` +> +> `top` +> +> `free` +> +> `dstat` +> +> `htop` +> +> `nmon` +> +> `uptime` + + + + + + + + + +## 4.shell编程 + + + + + +变量 + +> 命令替换符 +> +> 特殊变量 +> +> `$0¥` +> +> `$#` +> +> `$?` +> +> `$@` +> +> `$*` + + + +=两边没有空格 + + + +输入输出 + +> read +> +> echo +> +> printf + + + +分支 + +> test表达式 +> +> ​ man test +> +> 整数判断:整数计算 +> +> 字符串 == + + + + + +> if [[ ]] + +> case + + + +循环 for + +> ```shell +> for i in `seq 1 100` +> ``` +> +> `for (( i =1; i<= 100;i++ ))`(推荐) +> +> `while` +> +> `until` +> +> + + + +函数 + +数组 + +> 数组初始化 +> +> unset +> +> 遍历 + + + + + + + + + +![1](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/1.png) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git "a/02.c++\347\254\224\350\256\260/03.\345\237\272\347\241\200\346\225\260\346\215\256\347\273\223\346\236\204.md" "b/02.c++\347\254\224\350\256\260/03.\345\237\272\347\241\200\346\225\260\346\215\256\347\273\223\346\236\204.md" new file mode 100644 index 0000000..652bfc2 --- /dev/null +++ "b/02.c++\347\254\224\350\256\260/03.\345\237\272\347\241\200\346\225\260\346\215\256\347\273\223\346\236\204.md" @@ -0,0 +1,2711 @@ +[TOC] + + + + + + + +# 0.2020.12.22 + + + + + + + + + + + +# 1.顺序表与链表 + +## 1.1顺序表 + + + +![截屏2020-11-02 上午10.17.04](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-11-02%20%E4%B8%8A%E5%8D%8810.17.04.png) + +![截屏2020-11-02 上午10.19.36](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-11-02%20%E4%B8%8A%E5%8D%8810.19.36.png) + +![截屏2020-11-02 上午10.24.28](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-11-02%20%E4%B8%8A%E5%8D%8810.24.28.png) + + + + + +![截屏2020-11-02 上午10.39.19](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-11-02%20%E4%B8%8A%E5%8D%8810.39.19.png) + +![截屏2020-11-02 上午10.40.05](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-11-02%20%E4%B8%8A%E5%8D%8810.40.05.png) + + + +![截屏2020-11-02 下午3.42.57](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-11-02%20%E4%B8%8B%E5%8D%883.42.57.png) + +![截屏2020-11-02 下午3.43.57](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-11-02%20%E4%B8%8B%E5%8D%883.43.57.png) + +![截屏2020-11-02 下午3.45.11](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-11-02%20%E4%B8%8B%E5%8D%883.45.11.png) + + + + + +算法:在cpu资源有限的情况下,执行更多的命令 + +数据结构=结构定义+结构操作 + + + + + +顺序表:更高级的数组,连续的存储空间,可以存储任意类型 + + + +>malloc 仅开辟空间,calloc 开辟空间并且初始化,清空原来的值,realloc 重新划分空间 +>realloc(地址,字节数),返回值为新开辟空间的首地址, +>首先会在原来地址的后面开辟一倍的地址,并且返回原来的首地址, +>如果后面的取件无法扩建,找一个其他地方,为原来的两倍,将原来的内容拷贝到新开辟的空间,返回首地址 +>不一定开辟成功,降低需求开1/2,如果无法开辟空间返回值为NULL, + + ![截屏2021-02-02 下午10.45.51](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-02%20%E4%B8%8B%E5%8D%8810.45.51.png) + +![截屏2021-02-02 下午10.45.53](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-02%20%E4%B8%8B%E5%8D%8810.45.53.png) + +![截屏2021-02-02 下午10.45.55](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-02%20%E4%B8%8B%E5%8D%8810.45.55.png) + +![截屏2021-02-02 下午10.45.56](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-02%20%E4%B8%8B%E5%8D%8810.45.56.png) + +![截屏2021-02-02 下午10.45.58](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-02%20%E4%B8%8B%E5%8D%8810.45.58.png) + + + + + +## 1.2顺序表代码演示 + +```cpp +#include +#include +#include + +#define COLOR(a,b) "\033[" #b "m" a "\033[0m" +#define GREEN(a) COLOR(a, 32) + + +//结构定义 +typedef struct Vector { + int *data;//存储数据,记录连续开辟的存储空间的首地址 + int size, length;// +} Vec; + +Vec *init(int n) { + Vec *v = (Vec *)malloc(sizeof(Vec));//动态开辟空间 + v->data = (int *)malloc(sizeof(int) * n);//data指向开辟空间的首地址 + v->size = n;// + v->length = 0; + return v; +} + +int expand(Vec *v) { + //malloc 仅开辟空间,calloc 开辟空间并且初始化,清空原来的值,realloc 重新划分空间 + //realloc(地址,字节数),返回值为新开辟空间的首地址, + //首先会在原来地址的后面开辟一倍的地址,并且返回原来的首地址, + //如果后面的取件无法扩建,找一个其他地方,为原来的两倍,将原来的内容拷贝到新开辟的空间,返回首地址 + //不一定开辟成功,降低需求开1/2,如果无法开辟空间返回值为NULL, + int extr_size = v->size; + int *p; + while (extr_size) { + p = (int *)realloc(v->data, sizeof(int) * (v->size + extr_size));//开辟原来的2倍 + if (p) break;//开辟空间成功 + extr_size >>= 1;//开辟空间失败,降低需求,开辟1/2 + } + if (extr_size == 0) return 0; + v->data = p; + v->size += extr_size; + return 1; +} + +//插入 +int insert(Vec *v, int value, int index) { + //在v中的index位置插入值value + if (v == NULL) return 0; + if (index < 0 || index > v->length) return 0;//插入位置不合法 + if (v->length == v->size) {//顺序表已满,扩容 + if (!expand(v)) return 0; + printf(GREEN("success to expand! the Vector size is %d\n"), v->size); + } + for (int i = v->length; i > index; i--) { + v->data[i] = v->data[i - 1]; + } + v->data[index] = value; + v->length +=1; + return 1; +} + +//删除顺序表中第index位置的值 +int erase(Vec *v, int index) { + if (v == NULL) return 0; + if (index < 0 || index >= v->length) return 0;//插入位置不合法 + for (int i = index + 1; i < v->length; i++) { + v->data[i - 1] = v->data[i]; + } + v->length -= 1; + return 1; +} + +void clear(Vec *v) { + if(v == NULL) return ;//如果v为空 + free(v->data); + free(v);//释放malloc动态申请的内存 + return ; +} + +//输出线性表 +void output(Vec *v) { + if (v == NULL) return ; + printf("Vector : ["); + for (int i = 0; i < v->length; i++) { + i && printf(","); + printf("%d", v->data[i]); + } + printf("]\n"); + return ; +} + + +int main() { + srand(time(0)); + #define max_op 20 + Vec *v = init(2); + for (int i = 0; i < max_op; i++) { + ins + int index = rand() % (v->length + 3) -1;//index = [-1, length + 2],测试用例 + int op = rand() % 4; + switch (op) {//012插入3删除 + case 1: + case 2: + case 0: + printf("insert %d at %d to Vector = %d\n", value, index, insert(v, value, index)); + break; + case 3: + printf("erase a iterm at %d from Vector = %d\n", index, erase(v, index)); + break; + } + output(v); + printf("\n"); + } + clear(v); + #undef max_op + + return 0; +} + +``` + + + + + + + +## 1.3链表 + + + +![截屏2020-11-03 下午4.30.00](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-11-03%20%E4%B8%8B%E5%8D%888.10.29.png) + +![截屏2020-11-03 下午8.10.29](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-11-03%20%E4%B8%8B%E5%8D%888.10.29.png) + + + + + + + +![](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-12-17%20%E4%B8%8B%E5%8D%882.09.53.png) + + + + + +链表内部有数据和记录下一个结点的地址 + + + +![截屏2021-02-02 下午10.46.01](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-02%20%E4%B8%8B%E5%8D%8810.46.01.png) + +![截屏2021-02-02 下午10.46.02](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-02%20%E4%B8%8B%E5%8D%8810.46.02.png) + +![截屏2021-02-02 下午10.46.03](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-02%20%E4%B8%8B%E5%8D%8810.46.03.png) + +![截屏2021-02-02 下午10.46.05](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-02%20%E4%B8%8B%E5%8D%8810.46.05.png) + +![截屏2021-02-02 下午10.46.06](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-02%20%E4%B8%8B%E5%8D%8810.46.06.png) + +![截屏2021-02-02 下午10.46.07](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-02%20%E4%B8%8B%E5%8D%8810.46.07.png) + +![截屏2021-02-02 下午10.46.09](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-02%20%E4%B8%8B%E5%8D%8810.46.09.png) + +![截屏2021-02-02 下午10.46.10](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-02%20%E4%B8%8B%E5%8D%8810.46.10.png) + + + +## 1.4链表代码演示 + +```cpp +#include +#include +#include + +//结构定义 +typedef struct ListNode {//结点 + int data;//存储数据 + struct ListNode *next;//记录下一个结点的位置 +} ListNode; + +typedef struct List {//链表 + ListNode head;//虚拟头结点,list + int length; +} List; + +ListNode *createNewNode(int);//初始化结点 +List *createLinkList();//初始化链表 +void clear_node(ListNode *);//释放结点 +void clear(List *);//释放链表 +int insert(List *, int, int);//插入 +int erase(List *, int);//删除 +void output(List *);//打印链表 +void reverse(List *); + +int main() +{ + srand(time(0)); + #define max_op 20 + List *list = createLinkList(); + for (int i = 0; i < max_op; i++) { + int value = rand() % 100; + int index = rand() % (list->length + 3) - 1; + int op = rand() % 5; + switch (op) { + case 0: + case 1: + case 2:{ + printf("insert %d at %d to list = %d\n", value, index, insert(list, index, value)); + } + break; + case 3: + printf("earse a iterm at %d from list = %d\n", index, erase(list, index)); + break; + case 4:{ + printf("reverse the list!\n"); + reverse(list); + }break; + } + output(list); + printf("\n"); + } + #undef max_op + clear(list); + return 0; +} + +ListNode *createNewNode(int value){ + ListNode *node = (ListNode *)malloc(sizeof(ListNode)); + node->data = value; + node->next = NULL; + return node; +} + +List *createLinkList() { + List *list = (List *)malloc(sizeof(List)); + list->head.next = NULL; + list->length = 0; + return list; +} + +int insert(List *list, int index, int value) { + if (list == NULL) return 0; + if (index < 0 || index > list->length) return 0; + ListNode *p = &(list->head);//指向头结点 + ListNode *node = createNewNode(value);//初始化结点,准备插入链表中 + while (index--) p = p->next; + node->next = p->next; + p->next = node; + list->length += 1; + return 1; +} + +int erase(List *list, int index) { + if (list == NULL) return 0; + if (index < 0 || index >= list->length) return 0; + ListNode *p = &(list->head); + ListNode *delete_node; + while (index--) p = p->next; + delete_node = p->next; + p->next = delete_node->next; + free(delete_node); + list->length -=1; + return 1; +} + +void reverse(List *list) { + if (list == NULL) return ; + ListNode *p = list->head.next; + ListNode *q; + list->head.next = NULL; + while (p) { + q = p->next; + p->next = list->head.next; + list->head.next = p; + p = q; + } + return ; +} + +void output(List *list) { + if (list == NULL) return ; + printf("List(%d) = [", list->length); + for (ListNode *p = list->head.next; p; p = p->next) { + printf("%d->", p->data); + } + printf("NULL]\n"); + return ; +} + +void clear_node(ListNode *node) { + if (node == NULL) return ; + free(node); + return ; +} +void clear(List *list) { + if(list == NULL) return ; + ListNode *delete_node = list->head.next; + ListNode *temp; + while (delete_node) { + temp = delete_node->next; + free(delete_node); + delete_node = temp; + } + free(list); +} +``` + + + +## 1.5单向循环链表 + +![截屏2021-02-02 下午10.46.12](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-02%20%E4%B8%8B%E5%8D%8810.46.12.png) + +![截屏2021-02-02 下午10.46.13](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-02%20%E4%B8%8B%E5%8D%8810.46.13.png) + +![截屏2021-02-02 下午10.46.14](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-02%20%E4%B8%8B%E5%8D%8810.46.14.png) + +![截屏2021-02-02 下午10.46.15](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-02%20%E4%B8%8B%E5%8D%8810.46.15.png) + +![截屏2021-02-02 下午10.46.16](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-02%20%E4%B8%8B%E5%8D%8810.46.16.png) + + + + + + + + + +# 2.栈和队列 + + + +## 2.1栈 + +![截屏2020-11-04 下午3.19.10](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-11-04%20%E4%B8%8B%E5%8D%883.19.10.png) + +![截屏2020-11-04 下午3.20.10](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-11-04%20%E4%B8%8B%E5%8D%883.20.10.png) + +![截屏2020-11-04 下午3.20.41](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-11-04%20%E4%B8%8B%E5%8D%883.20.41.png) + +![截屏2020-11-05 上午10.37.49](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-11-05%20%E4%B8%8A%E5%8D%8810.37.49.png) + +![截屏2020-11-05 下午3.07.14](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-11-05%20%E4%B8%8B%E5%8D%883.07.14.png) + + + +线性数据结构 + +FILO (First In Last Out)先进后出 + + + +栈顶指针 + +对于一个空栈,栈顶指针下标为-1 + +![截屏2021-02-02 下午10.51.47](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-02%20%E4%B8%8B%E5%8D%8810.51.47.png) + +![截屏2021-02-02 下午10.51.48](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-02%20%E4%B8%8B%E5%8D%8810.51.48.png) + +![截屏2021-02-02 下午10.51.49](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-02%20%E4%B8%8B%E5%8D%8810.51.49.png) + +## 2.2栈代码演示 + +```cpp +#include +#include +#include + +#define COLOR(a, b) "\033[" #b "m" a "\033[0m" +#define GREEN(a) COLOR(a, 32) + +typedef struct Stack{ + int *data; + int top; + int size; +} Stack; + +Stack *init(int input_size) {//初始化栈 + Stack *stack = (Stack *)malloc(sizeof(Stack)); + stack->data = (int *)malloc(sizeof(int) * input_size); + stack->size = input_size; + stack->top = -1;//栈顶指针初始值为-1 + return stack; +} + +int top(Stack *stack) {//栈顶元素 + //if (empty(stack)) return 0; + return stack->data[stack->top]; +} + +int empty(Stack *stack) {//判空 + return stack->top == -1; +} + +int expand(Stack *stack) {//扩容 + int *temp_stack; + int extr_size = stack->size; + while (extr_size) { + temp_stack = (int *)realloc(stack->data, sizeof(int) * (extr_size + stack->size)); + if (temp_stack) break; + extr_size >= 1; + } + if (temp_stack == NULL) return 0; //扩容失败 + stack->size += extr_size; + stack->data = temp_stack; + return 1; +} + +int push(Stack *stack, int value) {//入栈 + if (stack == NULL) return 0; + if (stack->top == stack->size - 1){ //扩容 + if (!expand(stack)) return 0; + printf(GREEN("expand successful! stack->size = %d \n"), stack->size); + } + stack->data[++stack->top] = value; + return 1; +} + +int pop(Stack *stack) {//出栈 + if (stack == NULL) return 0; + if (empty(stack)) return 0; + stack->top--; + return 1; +} + +void output(Stack *stack) {//打印栈 + if (stack == NULL) return ; + if (empty(stack)) { + printf("stack empty!\n"); + return ; + } + printf("["); + for (int i = 0; i <= stack->top; i++) { + i && printf(","); + printf("%d", stack->data[i]); + } + printf("]\n"); + return ; +} + +void clear(Stack *stack) {//释放申请的内存 + if (stack == NULL) return ; + free(stack->data); + free(stack); + return ; +} + +int main() +{ + srand(time(0)); + #define max_op 20 + Stack *stack = init(2); + for (int i = 0; i < max_op; i++) { + int value = rand() % 100; + int op = rand() % 4; + switch (op) { + case 0: + case 1: + case 2: { + printf("push %d to the stack= %d\n", value, push(stack, value)); + }break; + case 3: { + printf("pop %d from the stack!", top(stack)); + printf("result = %d\n", pop(stack)); + }break; + + } + output(stack); + printf("\n"); + } + #undef max_op + clear(stack); + + return 0; +} +``` + + + + + +## 2.3队列 + +先进先出FIFO(First In First out) + + + + + +![截屏2020-11-04 上午9.17.30](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-11-04%20%E4%B8%8A%E5%8D%889.17.30.png) + +![截屏2020-11-04 上午9.19.23](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-11-04%20%E4%B8%8A%E5%8D%889.19.23.png) + +![截屏2020-11-04 上午9.19.46](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-11-04%20%E4%B8%8A%E5%8D%889.19.46.png) + +![截屏2020-11-04 上午9.21.18](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-11-04%20%E4%B8%8A%E5%8D%889.21.18.png) + +![截屏2020-11-04 上午11.30.30](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-11-04%20%E4%B8%8A%E5%8D%8811.30.30.png) + +![截屏2021-02-02 下午10.51.37](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-02%20%E4%B8%8B%E5%8D%8810.51.37.png) + +![截屏2021-02-02 下午10.51.39](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-02%20%E4%B8%8B%E5%8D%8810.51.39.png) + +![截屏2021-02-02 下午10.51.41](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-02%20%E4%B8%8B%E5%8D%8810.51.41.png) + +![截屏2021-02-02 下午10.51.42](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-02%20%E4%B8%8B%E5%8D%8810.51.42.png) + +![截屏2021-02-02 下午10.51.44](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-02%20%E4%B8%8B%E5%8D%8810.51.44.png) + +![截屏2021-02-02 下午10.51.45](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-02%20%E4%B8%8B%E5%8D%8810.51.45.png) + + + + + +## 2.4队列代码演示 + +### 1.简单队列 + +```cpp +#include +#include +#include + +//结构定义+结构操作 +typedef struct Queue { + int *data; + int head;//头结点 + int tail;//尾结点,最后一个元素的下一个为空的位置 + int length; +} Queue; + +//结构操作 +Queue *init(int n) {//初始化队列 + Queue *queue = (Queue *)malloc(sizeof(Queue)); + queue->data = (int *)malloc(sizeof(int) * n); + queue->head = queue->tail = 0; + queue->length = n; + return queue; +} + +int front(Queue *queue) { + return queue->data[queue->head]; +} + +int empty(Queue *queue){ + return queue->head == queue->tail; +} + +int push(Queue *queue, int value) { + if(queue == NULL) return 0; + if(queue->tail == queue->length) return 0; + queue->data[queue->tail++] = value; + //queue->tail++; + return 1; +} + +int pop(Queue *queue) { + if (queue == NULL) return 0; + if (empty(queue)) return 0; + queue->head++; + return 1; +} + +void output(Queue *queue) { + if (queue == NULL) return ; + printf("Queue : ["); + for (int i = queue->head, j = 0; i < queue->tail; i++,j++) { + j && printf(","); + printf("%d", queue->data[i]); + } + printf("]\n"); + return ; +} + +void clear(Queue *queue) {//清空队列 + if(queue == NULL) return ; + free(queue->data); + queue->data = NULL; + free(queue); + queue = NULL; + return ; +} + + +int main() { + srand(time(0)); + #define max_op 20 + Queue *queue = init(max_op); + for (int i = 0; i < max_op; i++) { + int value = rand() % 100; + int op = rand() % 4; + switch (op) { + case 0: + case 1: + case 2:{ + printf("push %d to the Queue !", value); + printf("result = %d\n", push(queue, value)); + + }break; + case 3: { + printf("pop %d from th Queue !", front(q)); + printf("result = %d\n", pop(queue)); + }break; + } + output(queue); + printf("\n"); + } + + #undef max_op + clear(queue); + return 0; +} +``` + + + + + +### 2.循环队列 + +(解决假溢出,出队时,头指针往后走,会造成假溢出) + + + +```cpp +#include +#include +#include + +//结构定义+结构操作 +typedef struct Queue { + int *data; + int head;//头结点 + int tail;//尾结点,最后一个元素的下一个为空的位置 + int length; + int count;//记录队列中元素的个数 +} Queue; + +//结构操作 +Queue *init(int n) {//初始化队列 + Queue *queue = (Queue *)malloc(sizeof(Queue)); + queue->data = (int *)malloc(sizeof(int) * n); + queue->head = queue->tail = 0; + queue->length = n; + queue->count = 0; + return queue; +} + +int front(Queue *queue) { + return queue->data[queue->head]; +} + +int empty(Queue *queue){ + // return queue->head == queue->tail; + return queue->count == 0; +} + +int push(Queue *queue, int value) { + if(queue == NULL) return 0; + if(queue->count == queue->length) return 0;// + queue->data[queue->tail++] = value; + //queue->tail++; + if (queue->tail == queue->length) queue->tail = 0;//queue->tail %= queue->length; + queue->count += 1; + return 1; +} + +int pop(Queue *queue) { + if (queue == NULL) return 0; + if (empty(queue)) return 0; + queue->head++; + if (queue->head == queue->length) queue->head = 0; + queue->count--; + return 1; +} + +void output(Queue *queue) { + if (queue == NULL) return ; + printf("Queue : ["); + for (int i = queue->head, j = 0; j < queue->count; i++,j++) { + j && printf(","); + printf("%d", queue->data[i % queue->length]); + } + printf("]\n"); + return ; +} + +void clear(Queue *queue) {//清空队列 + if(queue == NULL) return ; + free(queue->data); + queue->data = NULL; + free(queue); + queue = NULL; + return ; +} + + +int main() { + srand(time(0)); + #define max_op 20 + Queue *queue = init(max_op); + for (int i = 0; i < max_op; i++) { + int value = rand() % 100; + int op = rand() % 4; + switch (op) { + case 0: + case 1: + case 2:{ + printf("push %d to the Queue !", value); + printf("result = %d\n", push(queue, value)); + + }break; + case 3: { + printf("pop %d from th Queue !", value); + printf("result = %d\n", pop(queue)); + }break; + } + output(queue); + printf("\n"); + } + + #undef max_op + clear(queue); + return 0; +} +``` + + + +解决了假溢出但是会出现真溢出 + + + +### 3.循环队列2 + + + +```cpp +#include +#include +#include + +#define COLOR(a,b) "\033[" #b "m" a "\033[0m" +#define GREEN(a) COLOR(a,32) + +//结构定义+结构操作 +typedef struct Queue { + int *data; + int head;//头结点 + int tail;//尾结点,最后一个元素的下一个为空的位置 + int length; + int count;//记录队列中元素的个数 +} Queue; + +//结构操作 +Queue *init(int n) {//初始化队列 + Queue *queue = (Queue *)malloc(sizeof(Queue)); + queue->data = (int *)malloc(sizeof(int) * n); + queue->head = queue->tail = 0; + queue->length = n; + queue->count = 0; + return queue; +} + +int front(Queue *queue) { + return queue->data[queue->head]; +} + +int empty(Queue *queue){ + // return queue->head == queue->tail; + return queue->count == 0; +} + +int expand(Queue *queue) { + int extr_size = queue->length; + int *temp_queue; + while (extr_size) { + temp_queue = (int *)malloc(sizeof(int) * (extr_size + queue->length)); + if (temp_queue) break; + extr_size >>= 1; + } + if (temp_queue == NULL) return 0; + for (int i = queue->head, j = 0; j < queue->count; j++) { + temp_queue[j] = queue->data[(i + j) % queue->length]; + } + free(queue->data); + queue->data = temp_queue; + queue->length += extr_size; + queue->head = 0; + queue->tail = queue->count; + return 1; +} + +int push(Queue *queue, int value) { + if(queue == NULL) return 0; + if(queue->count == queue->length) {//扩容 + if(!expand(queue)) return 0; + printf(GREEN("ecpand sucessfull!Queue->size = %d\n"), queue->length); + } + queue->data[queue->tail++] = value; + //queue->tail++; + if (queue->tail == queue->length) queue->tail = 0;//queue->tail %= queue->length; + queue->count += 1; + return 1; +} + +int pop(Queue *queue) { + if (queue == NULL) return 0; + if (empty(queue)) return 0; + queue->head++; + if (queue->head == queue->length) queue->head = 0; + queue->count--; + return 1; +} + +void output(Queue *queue) { + if (queue == NULL) return ; + printf("Queue : ["); + for (int i = queue->head, j = 0; j < queue->count; i++,j++) { + j && printf(","); + printf("%d", queue->data[i % queue->length]); + } + printf("]\n"); + return ; +} + +void clear(Queue *queue) {//清空队列 + if(queue == NULL) return ; + free(queue->data); + queue->data = NULL; + free(queue); + queue = NULL; + return ; +} + + +int main() { + srand(time(0)); + #define max_op 20 + Queue *queue = init(2); + for (int i = 0; i < max_op; i++) { + int value = rand() % 100; + int op = rand() % 4; + switch (op) { + case 0: + case 1: + case 2:{ + printf("push %d to the Queue !", value); + printf("result = %d\n", push(queue, value)); + + }break; + case 3: { + printf("pop %d from th Queue !", value); + printf("result = %d\n", pop(queue)); + }break; + } + output(queue); + printf("\n"); + } + + #undef max_op + clear(queue); + return 0; +} +``` + + + + + + + +# 3.树 + + + +## 3.1.二叉树 + + + + + +二叉树性质 + + + + + +![截屏2020-11-05 下午4.56.15](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-11-05%20%E4%B8%8B%E5%8D%884.56.15.png) + + + +![截屏2020-11-05 下午5.04.49](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-11-05%20%E4%B8%8B%E5%8D%885.04.49.png) + +![截屏2020-11-05 下午5.24.57](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-11-05%20%E4%B8%8B%E5%8D%885.24.57.png) + +![截屏2020-11-05 下午5.25.24](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-11-05%20%E4%B8%8B%E5%8D%885.25.24.png) + +![截屏2020-11-05 下午5.25.31](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-11-05%20%E4%B8%8B%E5%8D%885.25.31.png) + +![截屏2020-11-05 下午5.25.51](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-11-05%20%E4%B8%8B%E5%8D%885.25.51.png) + +![截屏2020-11-06 下午11.15.42](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-11-06%20%E4%B8%8B%E5%8D%8811.15.42.png) + +![截屏2020-11-06 下午11.17.03](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-11-06%20%E4%B8%8B%E5%8D%8811.17.03.png) + +![截屏2021-02-02 下午10.42.32](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-02%20%E4%B8%8B%E5%8D%8810.42.32.png) + +![截屏2021-02-02 下午10.42.36](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-02%20%E4%B8%8B%E5%8D%8810.42.36.png) + +![截屏2021-02-02 下午10.42.40](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-02%20%E4%B8%8B%E5%8D%8810.42.40.png) + +![截屏2021-02-02 下午10.42.42](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-02%20%E4%B8%8B%E5%8D%8810.42.42.png) + +![截屏2021-02-02 下午10.42.44](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-02%20%E4%B8%8B%E5%8D%8810.42.44.png) + +![截屏2021-02-02 下午10.42.47](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-02%20%E4%B8%8B%E5%8D%8810.42.47.png) + +![截屏2021-02-02 下午10.42.48](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-02%20%E4%B8%8B%E5%8D%8810.42.48.png) + +![截屏2021-02-02 下午10.42.50](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-02%20%E4%B8%8B%E5%8D%8810.42.50.png) + +## 3.2.二叉树代码演示 + +```cpp +#include +#include +#include + +typedef struct Node {//定义节点 + int data; + struct Node *lchild, *rchild; +} Node; + +typedef struct Tree {//定义树 + Node *root; + int n;//节点个数 +} Tree; + +Node *getNewNode (int value) {//节点初始化 + Node *node = (Node *)malloc(sizeof(Node)); + node->data = value; + node->lchild = node->rchild = NULL; + return node; +} + +Tree *getNewTree() {//初始化树 + Tree *tree = (Tree *)malloc(sizeof(Tree)); + tree->root = NULL; + tree->n = 0; + return tree; +} + +Node *insert_node(Node *root, int value, int *flag) {// + if (root == NULL) { + *flag = 1; + return getNewNode(value); + } + if (root->data == value) return root; + if (root->data > value) root->lchild = insert_node(root->lchild, value, flag);//递归插入,直到节点为空 + else root->rchild = insert_node(root->rchild, value, flag); + return root; +} + +void insert(Tree *tree, int value) {//二叉查找树/二叉排序树 + int flag = 0; + tree->root = insert_node(tree->root, value, &flag); + tree->n += flag; + return ; +} + +void clearNode(Node *node) {//释放节点 + if (node == NULL) return ; + clearNode(node->lchild); + clearNode(node->rchild); + free(node); + return ; +} + +void clear(Tree *tree) {//释放树申请的内存 + if (tree == NULL) return ; + clearNode(tree->root); + free(tree); + return ; +} + +//树的遍历 +//1.先序遍历 +void pre_order_node(Node *node) {//先序遍历(递归) + if (node == NULL) return ; + printf("%d ", node->data); + pre_order_node(node->lchild); + pre_order_node(node->rchild); + return ; +} +void pre_order(Tree *tree) {//先序遍历 + if (tree == NULL) return ; + printf("pre_order : "); + pre_order_node(tree->root); + printf("\n"); + return ; +} + +//2.中序遍历 +void in_order_node(Node *node) { + if (node == NULL) return ; + in_order_node(node->lchild); + printf("%d ", node->data); + in_order_node(node->rchild); + return ; +} +void in_order(Tree *tree) {//中序遍历 + if (tree == NULL) return ; + printf("pre_order : "); + in_order_node(tree->root); + printf("\n"); + return ; +} + + +//3.后序遍历 +void post_order_node(Node *node) { + if (node == NULL) return ; + post_order_node(node->lchild); + post_order_node(node->rchild); + printf("%d ", node->data); + return ; +} +void post_order(Tree *tree) {//后序遍历 + if (tree == NULL) return ; + printf("pre_order : "); + post_order_node(tree->root); + printf("\n"); + return ; +} + +void output_node(Node *root) { + if (root == NULL) return ; + printf("%d", root->data); + if (root->lchild == NULL && root->rchild == NULL) return ; + printf("("); + output_node(root->lchild); + printf(","); + output_node(root->rchild); + printf(")"); + return ; +} + +void output(Tree *tree) {//输出广义表 + if (tree == NULL) return ; + printf("tree(%d) : ", tree->n); + output_node(tree->root); + printf("\n"); + return ; +} + + +int main() { + srand(time(0)); + Tree *tree = getNewTree(); + #define max_op 4 + for (int i = 0; i < max_op; i++) { + int value = rand() % 100; + insert(tree, value); + output(tree); + } + pre_order(tree); + in_order(tree); + post_order(tree); + #undef max_op + clear(tree); + + return 0; +} +``` + + + + + + + + + +## 3.3.广义表转二叉树 + +![截屏2021-02-02 下午10.42.51](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-02%20%E4%B8%8B%E5%8D%8810.42.51.png) + +![截屏2021-02-02 下午10.42.53](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-02%20%E4%B8%8B%E5%8D%8810.42.53.png) + + + +```cpp +#include +#include +#include +#include + +typedef struct Node { + char data; + struct Node *lchild, *rchild; +} Node; + +typedef struct Tree { + Node *root; + int n; +} Tree; + +typedef struct Stack { + Node **data; + int top; + int size; +} Stack; + +Node *getNewNode(char value) { + Node *p = (Node *)malloc(sizeof(Node)); + p->data = value; + p->lchild = p->rchild = NULL; + return p; +} + +Tree *getNewTree() { + Tree *tree = (Tree *)malloc(sizeof(Tree)); + tree->root = NULL; + tree->n = 0; + return tree; +} + +Stack *init_stack(int n) { + Stack *stack = (Stack *)malloc(sizeof(Stack)); + stack->data = (Node **)malloc(sizeof(Node *) * n); + stack->top = -1; + stack->size = n; + return stack; +} + +Node *top(Stack *stack) { + return stack->data[stack->top]; +} + +int empty(Stack *stack) { + return stack->top == -1; +} + +int push(Stack *stack, Node *value) { + if (stack == NULL) return 0; + if (stack->top == stack->size - 1) return 0; + stack->data[++(stack->top)] = value; + return 1; +} + +int pop(Stack *stack) { + if (stack == NULL) return 0; + if (empty(stack)) return 0; + stack->top--; + return 1; +} + +void clear_stack(Stack *stack) { + if (stack == NULL) return ; + free(stack->data); + free(stack); + return ; +} + +void clear_node(Node *node) { + if (node == NULL) return ; + clear_node(node->lchild); + clear_node(node->rchild); + free(node); + return ; +} + +void clear_tree(Tree *tree) { + if (tree == NULL) return ; + clear_node(tree->root); + free(tree); + return ; +} +Node *build(char *str, int *node_num) { + Stack *stack = init_stack(strlen(str)); + int flag = 0;//标记逗号,确定是左孩子还是右孩子,flag = 0,在逗号左侧,左孩子, 1 在逗号右侧右孩子 + Node *temp = NULL;//临时保存新节点 + Node *p = NULL; + while (str[0]) { + switch (str[0]) { + case '(':{ + push(stack, temp); + flag = 0; + }break; + case ')':{ + p = top(stack); + pop(stack); + }break; + case ',': { + flag = 1; + } break; + case ' ': break; + default: + temp = getNewNode(str[0]); + if (!empty(stack) && flag == 0) { + top(stack)->lchild = temp; + }else if (!empty(stack) && flag == 1) { + top(stack)->rchild = temp; + } + ++(*node_num); + break; + } + ++str; + } + clear_stack(stack); + if (temp && !p) p = temp;//判空 + return p; +} + +void pre_order_node(Node *root) { + if (root == NULL) return ; + printf("%c ", root->data); + pre_order_node(root->lchild); + pre_order_node(root->rchild); + return ; +} + +void pre_order(Tree *tree) { + if (tree == NULL) return ; + printf("pre_order : "); + pre_order_node(tree->root); + printf("\n"); + return ; +} + +void in_order_node(Node *root) { + if (root == NULL) return ; + in_order_node(root->lchild); + printf("%c ", root->data); + in_order_node(root->rchild); + return ; +} + +void in_order(Tree *tree) { + if (tree == NULL) return ; + printf("in_order : "); + in_order_node(tree->root); + printf("\n"); + return ; +} + +void post_order_node(Node *root) { + if (root == NULL) return ; + post_order_node(root->lchild); + printf("%c ", root->data); + post_order_node(root->rchild); + return ; +} + +void post_order(Tree *tree) { + if (tree == NULL) return ; + printf("post_order : "); + post_order_node(tree->root); + printf("\n"); + return ; +} + + +int main() { + char str[1000] = {0}; + int node_num = 0; + scanf("%[^\n]s", str); + Tree *tree = getNewTree(); + tree->root = build(str, &node_num); + tree->n = node_num; + pre_order(tree); + in_order(tree); + post_order(tree); + clear_tree(tree); + + return 0; +} +``` + + + + + + + + + + + + + +```cpp +A(B(,D),C(E,)) +pre_order : A B D C E +in_order : B D A E C +post_order : B D A E C +``` + + + + + +# 4.排序与查找 + + + +> 稳定排序:插入排序、冒泡排序、归并排序 + + + +## 4.1插入排序 + +![截屏2021-02-02 下午10.55.58](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-02%20%E4%B8%8B%E5%8D%8810.55.58.png) + +![截屏2021-02-02 下午10.56.01](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-02%20%E4%B8%8B%E5%8D%8810.56.01.png) + +![截屏2021-02-02 下午10.56.02](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-02%20%E4%B8%8B%E5%8D%8810.56.02.png) + +![截屏2021-02-02 下午10.56.03](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-02%20%E4%B8%8B%E5%8D%8810.56.03.png) + +![截屏2021-02-02 下午10.56.04](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-02%20%E4%B8%8B%E5%8D%8810.56.04.png) + +![截屏2021-02-02 下午10.56.06](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-02%20%E4%B8%8B%E5%8D%8810.56.06.png) + +![截屏2021-02-02 下午10.56.07](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-02%20%E4%B8%8B%E5%8D%8810.56.07.png) + +![截屏2021-02-02 下午10.56.09](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-02%20%E4%B8%8B%E5%8D%8810.56.09.png) + +![截屏2021-02-02 下午10.56.10](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-02%20%E4%B8%8B%E5%8D%8810.56.10.png) + +$O(n^2)$ + + + +## 4.2冒泡排序O(N^2) + +![截屏2021-02-02 下午10.57.50](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-02%20%E4%B8%8B%E5%8D%8810.57.50.png) + +![截屏2021-02-02 下午10.57.53](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-02%20%E4%B8%8B%E5%8D%8810.57.53.png) + +![截屏2021-02-02 下午10.57.59](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-02%20%E4%B8%8B%E5%8D%8810.57.59.png) + +![截屏2021-02-02 下午10.58.00](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-02%20%E4%B8%8B%E5%8D%8810.58.00.png) + +![截屏2021-02-02 下午10.58.02](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-02%20%E4%B8%8B%E5%8D%8810.58.02.png) + +![截屏2021-02-02 下午10.58.04](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-02%20%E4%B8%8B%E5%8D%8810.58.04.png) + +![截屏2021-02-02 下午10.58.05](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-02%20%E4%B8%8B%E5%8D%8810.58.05.png) + +![截屏2021-02-02 下午10.58.06](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-02%20%E4%B8%8B%E5%8D%8810.58.06.png) + +![截屏2021-02-02 下午10.58.09](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-02%20%E4%B8%8B%E5%8D%8810.58.09.png) + +![截屏2021-02-02 下午10.58.11](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-02%20%E4%B8%8B%E5%8D%8810.58.11.png) + +![截屏2021-02-02 下午10.58.13](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-02%20%E4%B8%8B%E5%8D%8810.58.13.png) + +![截屏2021-02-02 下午10.58.14](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-02%20%E4%B8%8B%E5%8D%8810.58.14.png) + +![截屏2021-02-02 下午10.58.16](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-02%20%E4%B8%8B%E5%8D%8810.58.16.png) + + + + + + + + + + + + + + + +## 4.3归并排序O(n log n) + +![截屏2021-02-02 下午10.58.20](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-02%20%E4%B8%8B%E5%8D%8810.58.20.png) + +![截屏2021-02-02 下午10.58.22](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-02%20%E4%B8%8B%E5%8D%8810.58.22.png) + +![截屏2021-02-02 下午11.00.36](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-02%20%E4%B8%8B%E5%8D%8811.00.36.png) + +![截屏2021-02-02 下午11.00.37](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-02%20%E4%B8%8B%E5%8D%8811.00.37.png) + +![截屏2021-02-02 下午11.00.39](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-02%20%E4%B8%8B%E5%8D%8811.00.39.png) + +![截屏2021-02-02 下午11.00.40](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-02%20%E4%B8%8B%E5%8D%8811.00.40.png) + +![截屏2021-02-02 下午11.00.41](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-02%20%E4%B8%8B%E5%8D%8811.00.41.png) + +![截屏2021-02-02 下午11.00.43](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-02%20%E4%B8%8B%E5%8D%8811.00.43.png)![截屏2021-02-02 下午11.00.45](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-02%20%E4%B8%8B%E5%8D%8811.00.45.png) + +![截屏2021-02-02 下午11.00.46](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-02%20%E4%B8%8B%E5%8D%8811.00.46.png) + +![截屏2021-02-02 下午11.00.48](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-02%20%E4%B8%8B%E5%8D%8811.00.48.png) + +![截屏2021-02-02 下午11.00.49](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-02%20%E4%B8%8B%E5%8D%8811.00.49.png) + +![截屏2021-02-02 下午11.00.50](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-02%20%E4%B8%8B%E5%8D%8811.00.50.png) + + + +## 4.4稳定排序代码演示 + +稳定排序:排序前a[i]=a[j] (i +#include +#include +#include +using namespace std; + +#define swap(a, b) {\ + a ^= b; b^= a; a ^= b;\ +} + +#define TEST(arr, n, func, args...) {\ + int *num = (int *)malloc(sizeof(int) * n);\ + memcpy(num, arr, sizeof(int) * n);\ + output(num, n);\ + printf("%s : \n", #func);\ + func(args);\ + output(num, n);\ + free(num);\ +} + +//插入排序 +void insert_sort(int *num, int n) { + for (int i = 1; i < n; i++) { + for (int j = i; j > 0 && num[j] < num[j - 1]; j--) { + swap(num[j], num[j - 1]); + } + } + return ; +} + +//冒泡排序 +void bubble_sort(int *num, int n) { + int times; + for (int i = 1; i < n && times; i++) {//times,如果有一轮排序没有发生互换位置,则终止循环 + times = 0; + for (int j = 0; j < n - 1; j++) { + if (num[j] <= num[j+ 1]) continue; + swap(num[j], num[j + 1]); + times++; + //if (num[j] > num[j + 1]) { + // swap(num[j], num[j + 1]); + // times++; + //} + } + } +} + +//归并排序 +void merage_sort(int *num, int l, int r) { + if (r - l <= 1) { + if (r - l == 1 && num[r] < num[l]) { + swap(num[r], num[l]); + } + return ; + } + //数组分为两段 + int mid = (l + r) >> 1; + merage_sort(num, l, mid); + merage_sort(num, mid + 1, r); + int *temp = (int *)malloc(sizeof(int) * (r - l + 1)); + //将两个有序的数组合并为一个 + int p1 = l, p2 = mid + 1, k = 0; + while (p1 <= mid || p2 <= r) {//当数组中有元素时进行合并 + if (p2 > r || (p1 <= mid && num[p1] < num[p2])) { + //p2 > r : 第二个数组已经没有元素了,p1 <= mid,第一个元素还有元素, + temp[k++] = num[p1++];//将两个数组中较小的那个值,赋值给temp,temp成升序排列 + } else { + temp[k++] = num[p2++]; + } + } + memcpy(num + l, temp, sizeof(int) * (r - l + 1));//将temp拷贝到num中 + free(temp); + return ; +} + +void randint(int *num, int n) {//随机生成100以内的数字 + while (n--) num[n] = rand() % 100; + return ; +} + +void output(int *num, int n) { + printf("["); + for (int i = 0; i < n; i++) { + printf("%d ", num[i]); + } + printf("]\n"); + return ; +} + +int main() { + //随机生成数组 + srand(time(0)); + #define max_n 20 + int arr[max_n]; + randint(arr, max_n); + TEST(arr, max_n, insert_sort, num, max_n); + TEST(arr, max_n, bubble_sort, num, max_n); + TEST(arr, max_n, merage_sort, num, 0, max_n - 1); + + #undef max_n + + + + return 0; +} +``` + + + +``` +[12 81 49 86 84 56 82 94 97 49 10 69 53 80 77 83 99 22 80 26 ] +insert_sort : +[10 12 22 26 49 49 53 56 69 77 80 80 81 82 83 84 86 94 97 99 ] +[12 81 49 86 84 56 82 94 97 49 10 69 53 80 77 83 99 22 80 26 ] +bubble_sort : +[10 12 22 26 49 49 53 56 69 77 80 80 81 82 83 84 86 94 97 99 ] +[12 81 49 86 84 56 82 94 97 49 10 69 53 80 77 83 99 22 80 26 ] +merage_sort : +[10 12 22 26 49 49 53 56 69 77 80 80 81 82 83 84 86 94 97 99 ] +``` + + + +## 4.5选择排序(O(N^2)) + +![截屏2021-02-02 下午11.02.24](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-02%20%E4%B8%8B%E5%8D%8811.02.24.png) + +![截屏2021-02-02 下午11.02.25](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-02%20%E4%B8%8B%E5%8D%8811.02.25.png) + +![截屏2021-02-02 下午11.02.26](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-02%20%E4%B8%8B%E5%8D%8811.02.26.png) + +![截屏2021-02-02 下午11.02.28](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-02%20%E4%B8%8B%E5%8D%8811.02.28.png) + + + + + + + +## 4.6快速排序(O(NlogN) ~ O(N^2)) + +![截屏2021-02-02 下午11.03.33](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-02%20%E4%B8%8B%E5%8D%8811.03.33.png) + +![截屏2021-02-02 下午11.03.35](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-02%20%E4%B8%8B%E5%8D%8811.03.35.png) + +![截屏2021-02-02 下午11.03.36](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-02%20%E4%B8%8B%E5%8D%8811.03.36.png) + +![截屏2021-02-02 下午11.03.38](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-02%20%E4%B8%8B%E5%8D%8811.03.38.png) + +![截屏2021-02-02 下午11.03.39](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-02%20%E4%B8%8B%E5%8D%8811.03.39.png) + +![截屏2021-02-02 下午11.03.41](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-02%20%E4%B8%8B%E5%8D%8811.03.41.png) + +![截屏2021-02-02 下午11.03.42](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-02%20%E4%B8%8B%E5%8D%8811.03.42.png) + +![截屏2021-02-02 下午11.03.44](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-02%20%E4%B8%8B%E5%8D%8811.03.44.png) + +![截屏2021-02-02 下午11.03.45](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-02%20%E4%B8%8B%E5%8D%8811.03.45.png) + +![截屏2021-02-02 下午11.03.47](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-02%20%E4%B8%8B%E5%8D%8811.03.47.png) + +![截屏2021-02-02 下午11.03.48](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-02%20%E4%B8%8B%E5%8D%8811.03.48.png) + + + + + + + +## 4.7非稳定排序代码演示 + + + +```cpp +#include +#include +#include +#include +#include +using namespace std; + +#define swap(a, b) {\ + __typeof(a) __temp = a;\ + a = b; b = __temp;\ +} + +#define TEST(arr, n, func, args...) {\ + int *num = (int *)malloc(sizeof(int) * n);\ + memcpy(num, arr, sizeof(int) * n);\ + output(num, n);\ + printf("%s = ", #func);\ + func(args);\ + output(num, n);\ + free(num);\ +} + + +void select_sort(int *num, int n) { // 选择排序 + for (int i = 0; i < n - 1; i++) { + int ind = i; //待排序去下标 + for (int j = i + 1; j < n; j++) { + if (num[ind] > num[j]) ind = j; + } + swap(num[i], num[ind]); + } + return ; +} + +void quick_sort(int *num, int l, int r) { // 快排 + if (l > r) return ; + int x = l, y = r, z = num[x]; + while (x < y) { + while (x < y && num[y] > z) y--; + if (x < y) num[x++] = num[y]; + while (x < y && num[x] < z) x++; + if (x < y) num[y--] = num[x]; + } + num[x] = z; + quick_sort(num, l, x - 1); + quick_sort(num, x + 1, r); + return ; +} + + + +void randint(int *num, int n) { // 生成n个100以内的随机数 + while (n--) num[n] = rand() % 100; + return ; +} + +void output(int *num, int n) { + cout << "["; + for (int i = 0; i < n; i++) { + cout << num[i] << " "; + } + cout << "]" << endl; + return ; +} + +int main() { + srand(time(0)); + #define MAX_N 10 + int arr[MAX_N]; + randint(arr, MAX_N); + + TEST(arr, MAX_N, select_sort, num, MAX_N); + TEST(arr, MAX_N, quick_sort, num, 0, MAX_N - 1); + + + #undef MAX_N + return 0; +} + +``` + + + + + +```cpp +#include +#include +using namespace std; + +void print(int *num, int n) { + for (int i = 0; i < n; i++) { + cout << num[i] << " "; + } + cout << endl; +} + +void quick_sort(int *num, int l, int r) { // 快排, 从左到右依次作为基准值, 将比基准值小的放左边, 大的放右边 + if (l > r) return ; + int x = l, y = r, z = num[x]; // 左右区间, 基准值 + while (x < y) { + while (x < y && num[y] > z) y--; // 如果尾指针 < 基准值,尾指针前移,找到第一个比基准值小的 + if (x < y) num[x++] = num[y]; // 将找到的比基准值小的移动到头指针处,头指针后移 + while (x < y && num[x] < z) x++; // 如果头部指针 < 基准值,头部指针后移,找到第一个比基准值大的 + if (x < y) num[y--] = num[x]; // 将比基准值大的移动到到尾部指针的位置,尾部指针前移 + //num[x] = z; // 基准值放在 x == y 处 + //print(num, 10); + } + num[x] = z; // 基准值放在 x == y 处 + print(num, 10); + //cout << "l = " << l << ", x - 1 = " << x - 1 << endl; + quick_sort(num, l, x - 1); + //cout << "x +1 = " << x + 1 << ", r = " << r << endl; + quick_sort(num, x + 1, r); +} + + +int main() { + int num[10] = {5, 7, 8, 6, 4, 3, 1, 2, 7, 5}; + int num1[10] = {5, 7, 8, 6, 4, 3, 1, 2, 7, 5}; + print(num, 10); + quick_sort(num, 0, 9); + + cout << "sort" << endl; + sort(num, num + 9); + print(num, 10); + + return 0; +} + + +``` + + + +## 4.8快速排序的优化 + +```cpp +#include +using namespace std; + +#define swap(a, b) {\ + __typeof(a) __temp = b;\ + b = a, a = __temp;\ +} + + +void quick_sort(int *num, int l, int r) { // 快速排序优化思路:基准值的选取可以随意选,可以先选择最中间的那个,2.递归改为循环 + if (l > r) return ; + // 如果选第一个作为基准值, 如果完全逆序,时间复杂度会退化,基准值选谁不重要,可以选择中间值做为基准值(二分查找) + // + while (l < r) { + int x = l, y = r, z = num[(l + r) >> 1]; // 左右指针, 基准值:以中间值作为基准值 + do { // + while (x <= y && num[x] < z) x++; + while (x <= y && num[y] > z) y--; + if (x <= y) { + swap(num[x], num[y]); + x++, y--; + } + } while (x <= y); + quick_sort(num, x, r); + r = y; + } + +} + + +int main() { + + int num[10] = {5, 7, 8, 6, 3, 4, 1, 2, 7, 5}; + quick_sort(num, 0, 9); + + for (int i = 0; i < 10; i++) { + cout << num[i] << " "; + } + + return 0; +} +``` + +```cpp +#include +#include +#include +#include +#include +using namespace std; + +#define swap(a, b) {\ + __typeof(a) __temp = b;\ + b = a, a = __temp;\ +} + +void quick_sortPlus(int *num, int l, int r) { + while (l < r) { + int x = l, y = r, z = num[(l + r) >> 1]; + do { + while (x <= y && num[x] < z) x++; + while (x <= y && num[y] > z) y--; + if (x <= y) { + swap(num[x], num[y]); + x++, y--; + } + } while (x <= y); + quick_sortPlus(num, x, r); + r = y; + } +} + +void randint(int *num, int n) { + while (n--) num[n] = rand() % 100; +} + + +void output(int *num, int n) { + cout << "["; + for (int i = 0; i < n; i++) { + cout << num[i] << " "; + } + cout << "]" << endl; + return ; +} +int main() { + + srand(time(0)); + #define MAX_N 20 + int arr[MAX_N]; + randint(arr, MAX_N); // 初始化随机值 + printf(" ===> "); + output(arr, MAX_N);\ + + printf("quick_sort++ = "); + quick_sortPlus(arr, 0, MAX_N - 1); + output(arr, MAX_N); + + #undef MAX_N + + return 0; +} + +``` + +```cpp + ===> [84 46 45 90 54 1 42 55 9 71 40 45 56 65 33 20 67 18 78 88 ] +quick_sort++ = [1 9 18 20 33 40 42 45 45 46 54 55 56 65 67 71 78 84 88 90 ] + +``` + + + +## 4.9二分查找 + +![截屏2021-02-02 下午11.03.58](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-02%20%E4%B8%8B%E5%8D%8811.03.58.png) + +![截屏2021-02-02 下午11.03.59](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-02%20%E4%B8%8B%E5%8D%8811.03.59.png) + +![截屏2021-02-02 下午11.04.01](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-02%20%E4%B8%8B%E5%8D%8811.04.01.png) + +![截屏2021-02-02 下午11.04.02](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-02%20%E4%B8%8B%E5%8D%8811.04.02.png) + +![截屏2021-02-02 下午11.04.04](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-02%20%E4%B8%8B%E5%8D%8811.04.04.png) + +![截屏2021-02-02 下午11.04.05](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-02%20%E4%B8%8B%E5%8D%8811.04.05.png) + +![截屏2021-02-02 下午11.04.07](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-02%20%E4%B8%8B%E5%8D%8811.04.07.png) + +![截屏2021-02-02 下午11.04.08](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-02%20%E4%B8%8B%E5%8D%8811.04.08.png) + +![截屏2021-02-02 下午11.04.10](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-02%20%E4%B8%8B%E5%8D%8811.04.10.png) + +![截屏2021-02-02 下午11.04.11](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-02%20%E4%B8%8B%E5%8D%8811.04.11.png) + +![截屏2021-02-02 下午11.04.13](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-02%20%E4%B8%8B%E5%8D%8811.04.13.png) + +![截屏2021-02-02 下午11.04.15](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-02%20%E4%B8%8B%E5%8D%8811.04.15.png) + + + + + + + +## 4.10三分查找 + +![截屏2021-02-02 下午11.04.16](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-02%20%E4%B8%8B%E5%8D%8811.04.16.png) + +![截屏2021-02-02 下午11.04.18](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-02%20%E4%B8%8B%E5%8D%8811.04.18.png) + + + + + +## 4.11哈希表 + +数组的性质:数组是展开的函数,函数是压缩的数组 + +数组可以由下表索引到值,时间复杂度$O(1$) + + + +> 哈希表:将任意类型的元素映射成数组下标 + +### 哈希表 + +![截屏2021-02-01 上午11.37.54](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-01%20%E4%B8%8A%E5%8D%8811.37.54.png) + + + +通过哈希函数映射成数组下标 + +16 % 9 = 7 + +![截屏2021-02-01 上午11.37.57](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-01%20%E4%B8%8A%E5%8D%8811.37.57.png) + + + + + +![截屏2021-02-01 上午11.37.59](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-01%20%E4%B8%8A%E5%8D%8811.37.59.png) + +7 % 9 = 7 发生冲突 + +![截屏2021-02-01 上午11.38.01](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-01%20%E4%B8%8A%E5%8D%8811.38.01.png) + +### 处理冲突 + +方法一:开放定值法:下一个位置是否有值,如果没有存放到下一个位置 + +方法二:再哈希:用第二种哈希方法处理 + +方法三:拉链法:值建立一个链表,所有的值都存放在链表中 + +方法四:建立公共溢出区 + +![截屏2021-02-01 上午11.38.03](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-01%20%E4%B8%8A%E5%8D%8811.38.03.png) + + + +![截屏2021-02-01 上午11.38.05](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-01%20%E4%B8%8A%E5%8D%8811.38.05.png) + + + +### 代码演示 + +```cpp +#include +#include +#include +//对字符串建立哈希表:BKDRHash +//冲突处理方法:拉链法 +//结构定义 + +typedef struct Node { + char *str; + struct Node *next; +} Node;//链表结点 + +typedef struct HashTable {//哈希表结构定义 + Node **data;//存Node* + int size; +} HashTable; + +Node *init_node(char *str, Node *head) {//初始化结点,将str插入到以head为头结点的链表中,头插法,所有的新节点都会插入到头部 + Node *p = (Node *)malloc(sizeof(Node)); + p->str = strdup(str);//符串拷贝 + p->next = head; + return p; +} + +HashTable *init_hashtable(int n) {//初始化哈希表,大小n + HashTable *h = (HashTable *)malloc(sizeof(HashTable)); + h->size = n << 1;//哈希表的利用率一定小于100%,一般为50%~90% + h->data = (Node **)calloc(h->size, sizeof(Node *)); + return h; +} + +int BKDRHash(char *str) { + int seed = 31;//初始化映射种子 + int hash = 0; + for (int i = 0; str[i]; i++) hash = hash * seed + str[i]; + return hash & 0x7fffffff;//防止hash变成负数,将符号位变成正数 +} + +int insert(HashTable *h, char *str) {// + int hash = BKDRHash(str);//将字符串映射成整型数组下表 + int ind = hash % h->size; + h->data[ind] = init_node(str, h->data[ind]); + return 1; +} + +int search(HashTable *h, char *str) { + int hash = BKDRHash(str); + int ind = hash % h->size; + Node *p = h->data[ind]; + while (p && strcmp(p->str, str)) p = p->next; + return p != NULL; +} + +void clear_node(Node *node) { + if (node == NULL) return ; + Node *p = node, *q; + while (p) { + q = p->next; + free(p->str); + p = q; + } + return ; +} + +void clear_hashtable(HashTable *h) { + if (h == NULL) return ; + for (int i = 0; i < h->size; i++) { + clear_node(h->data[i]); + } + free(h->data); + free(h); + return ; +} + +int main() { + int op; + #define max_n 100 + char str[max_n + 5] = {0}; + HashTable *h = init_hashtable(max_n + 5); + while (~scanf("%d%s", &op, str)) { + switch(op) { + case 0: + printf("insert %s to HashTable\n", str); + insert(h, str); + break; + case 1: + printf("search %s fromm HashTable result = %d \n", str, search(h, str)); + break; + } + + } + #undef max_n + clear_hashtable(h); + + + + return 0; +} +``` + + + +``` +0 hello +insert hello to HashTable +0 haizei +insert haizei to HashTable +0 nihao +insert nihao to HashTable +1 haizei +search haizei fromm HashTable result = 1 +1 haizeix +search haizeix fromm HashTable result = 0 +1 nihao +search nihao fromm HashTable result = 1 +1 hello +search hello fromm HashTable result = 1 +^C +``` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +# 5.堆与优先队列 + +## 5.1完全二叉树 + + + +![截屏2021-01-31 下午9.36.20](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-01-31%20%E4%B8%8B%E5%8D%889.36.20.png) + + + +完全二叉树:每一层都是满的,只有在最后一层不是满的 + +![截屏2021-01-31 下午9.36.23](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-01-31%20%E4%B8%8B%E5%8D%889.36.23.png) + +二叉树可以用数组存储 + + + +![截屏2021-01-31 下午9.36.29](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-01-31%20%E4%B8%8B%E5%8D%889.36.29.png) + +## 5.2堆 + +![截屏2021-01-31 下午9.36.31](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-01-31%20%E4%B8%8B%E5%8D%889.36.31.png) + + + +## 5.3堆尾部插入 + +自下向上调整 + +![截屏2021-01-31 下午9.36.35](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-01-31%20%E4%B8%8B%E5%8D%889.36.35.png) + +![截屏2021-01-31 下午9.36.37](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-01-31%20%E4%B8%8B%E5%8D%889.36.37.png) + +![](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-01-31%20%E4%B8%8B%E5%8D%889.36.38.png) + +![截屏2021-01-31 下午9.36.40](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-01-31%20%E4%B8%8B%E5%8D%889.36.40.png) + + + + + +## 5.4堆头部弹出/删除元素 + + + +![截屏2021-01-31 下午9.36.42](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-01-31%20%E4%B8%8B%E5%8D%889.36.42.png) + +取出最后一个元素当做头部元素 + +![截屏2021-01-31 下午9.36.44](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-01-31%20%E4%B8%8B%E5%8D%889.36.44.png) + +维护堆性质 + +![截屏2021-01-31 下午9.36.45](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-01-31%20%E4%B8%8B%E5%8D%889.36.45.png) + +![截屏2021-01-31 下午9.36.47](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-01-31%20%E4%B8%8B%E5%8D%889.36.47.png) + + + + + +## 5.5堆排序O(NlogN) + +![截屏2021-01-31 下午9.36.48](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-01-31%20%E4%B8%8B%E5%8D%889.36.48.png) + +![截屏2021-01-31 下午9.36.50](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-01-31%20%E4%B8%8B%E5%8D%889.36.50.png) + +![截屏2021-01-31 下午9.36.52](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-01-31%20%E4%B8%8B%E5%8D%889.36.52.png) + +![截屏2021-01-31 下午9.36.53](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-01-31%20%E4%B8%8B%E5%8D%889.36.53.png) + + + +## 5.6堆/优先队列 + +![截屏2021-01-31 下午9.36.55](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-01-31%20%E4%B8%8B%E5%8D%889.36.55.png) + +![截屏2021-01-31 下午9.36.56](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-01-31%20%E4%B8%8B%E5%8D%889.36.56.png) + +## 5.7优先队列代码实现 + +```c +// 大顶堆 +#include +#include +#include + +#define swap(a, b) {\ + __typeof(a) __temp = a;\ + a = b, b = __temp;\ +} + +typedef struct priority_queue { + int *data; + int cnt, size;//记录当前元素个数、全部元素数量 +} priority_queue; + +priority_queue *init(int n) { + priority_queue *q = (priority_queue *)malloc(sizeof(priority_queue)); + q->data = (int *)malloc(sizeof(int) * (n + 1));//下标从1开始 + q->cnt = 0; + q->size = n; + return q; +} + +int empty(priority_queue *q) { + return q->cnt == 0; +} + +int top(priority_queue *q) {//输出队首元素 + return q->data[1]; +} + +int push(priority_queue *q, int val ) { //插入元素val + if (q == NULL) return 0;//优先队列不存在 + if (q->cnt == q->size) return 0;//优先队列满了,不能插入 + //q->cnt++; + q->data[++q->cnt] = val; + //优先队列调整,自下向上 + int ind = q->cnt; + while (ind >> 1 && q->data[ind] > q->data[ind >> 1]) {//当当前节点有父节点,且当前节点>父节点的值(大顶堆) + swap(q->data[ind], q->data[ind >> 1]); + ind >>= 1; + } + return 1; +} +int pop(priority_queue *q) {//头部弹出,堆顶弹出且调整 + if (q == NULL) return 0;//优先队列不存在 + if (empty(q)) return 0;//优先队列为空 + q->data[1] = q->data[q->cnt--];//取优先队列的最后一个元素赋值给头部,头部弹出 + //q->cnt--; + //维护优先队列,自顶向下 + int ind = 1; + while ((ind << 1) <= q->cnt) {//当前优先队列还有子节点 + int temp = ind, l = ind << 1, r = ind << 1 | 1;// r = ind * 2 + 1 + if (q->data[l] > q->data[temp]) temp = l; + if (r <= q->cnt && q->data[r] > q->data[temp]) temp = r;//不一定有右孩子 + if (temp == ind) break;//当前节点就是三元组中最大的 + swap(q->data[ind], q->data[temp]); + ind = temp; + } + return 1; +} + +void clear(priority_queue * q) { + if (q == NULL) return ; + free(q->data); + free(q); + return ; +} + +int main() { + srand(time(0)); + #define max_op 20 + priority_queue *q = init(max_op); + for (int i = 0; i < max_op; i++) { + int val = rand() % 100; + push(q, val); + printf("insert %d to the priority_queue !\n", val); + } + for (int i = 0; i < max_op; i++) { + printf("%d ", top(q)); + pop(q); + } + printf("\n"); + #undef man_op + clear(q); + return 0; +} + +``` + + + +```shell +insert 9 to the priority_queue ! +insert 33 to the priority_queue ! +insert 31 to the priority_queue ! +insert 51 to the priority_queue ! +insert 86 to the priority_queue ! +insert 36 to the priority_queue ! +insert 68 to the priority_queue ! +insert 46 to the priority_queue ! +insert 65 to the priority_queue ! +insert 32 to the priority_queue ! +insert 64 to the priority_queue ! +insert 9 to the priority_queue ! +insert 42 to the priority_queue ! +insert 64 to the priority_queue ! +insert 28 to the priority_queue ! +insert 77 to the priority_queue ! +insert 48 to the priority_queue ! +insert 66 to the priority_queue ! +insert 27 to the priority_queue ! +insert 5 to the priority_queue ! +86 77 68 66 65 64 64 51 48 46 42 36 33 32 31 28 27 9 9 5 +//弹出元素从大到小 +``` + + + +## 5.8线性建堆 + +```cpp + +``` + + + + + + + + + + + +# 6.森林与并查集O(n) + + + +森林与并查集存在的意义:连通性 + +## 6.1Quick-Find算法 + +![截屏2021-04-24 上午9.39.00](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-04-24%20%E4%B8%8A%E5%8D%889.39.00.png) + +![截屏2021-04-24 上午9.39.03](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-04-24%20%E4%B8%8A%E5%8D%889.39.03.png) + +![截屏2021-04-24 上午9.39.04](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-04-24%20%E4%B8%8A%E5%8D%889.39.04.png) + +![截屏2021-04-24 上午9.39.06](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-04-24%20%E4%B8%8A%E5%8D%889.39.06.png) + +![截屏2021-04-24 上午9.39.08](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-04-24%20%E4%B8%8A%E5%8D%889.39.08.png) + +![截屏2021-04-24 上午9.39.09](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-04-24%20%E4%B8%8A%E5%8D%889.39.09.png) + +![截屏2021-04-24 上午9.39.10](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-04-24%20%E4%B8%8A%E5%8D%889.39.10.png) + +![截屏2021-04-24 上午9.39.12](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-04-24%20%E4%B8%8A%E5%8D%889.39.12.png) + +![截屏2021-04-24 上午9.39.13](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-04-24%20%E4%B8%8A%E5%8D%889.39.13.png) + +![截屏2021-04-24 上午9.39.15](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-04-24%20%E4%B8%8A%E5%8D%889.39.15.png) + +![截屏2021-04-24 上午9.39.16](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-04-24%20%E4%B8%8A%E5%8D%889.39.16.png) + +![截屏2021-04-24 上午9.39.18](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-04-24%20%E4%B8%8A%E5%8D%889.39.18.png) + +## 6.2Quick-Find代码演示 + +```cpp +#include +using namespace std; + +typedef struct UnionSet { + int *color; // 标记颜色 + int n; // 大小 +} UnionSet; + +UnionSet *init(int n) { // 并查集初始化 + UnionSet *u = (UnionSet *)malloc(sizeof(UnionSet)); // 初始化 + u->color = (int *)malloc(sizeof(int) * (n + 1)); // 从1号位开始存储 + u->n = n; + // 初始化颜色 + for (int i = 1; i <= n; i++) { + u->color[i] = i; + } + return u; +} + +int find(UnionSet *u, int x) { // 查找操作,查找x + return u->color[x]; // 返回当前元素的颜色 +} + +int merage(UnionSet *u, int a, int b) { // 合并操作, 合并a,b + if (find(u, a) == find(u, b)) return 0; // a,b本身就有连通关系 + // 染色 + int color_a = u->color[a]; + for (int i = 1; i <= u->n; i++) { + if (u->color[i] != color_a) continue; // if (u->color[i] - color_a) + u->color[i] = u->color[b]; // 找到和u->color[a] 一样的颜色 + } + return 1; +} + +void clear(UnionSet *u) { + if (u == NULL) return ; + free(u->color); + free(u); + return ; +} + + +int main() { + + return 0; +} +``` + + + +## 6.3Quick-Find解题 + +![截屏2021-04-24 上午10.23.41](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-04-24%20%E4%B8%8A%E5%8D%8810.23.41.png) + +```cpp +#include +#include +using namespace std; + +typedef struct UnionSet { + int *color; // 标记颜色 + int n; // 大小 +} UnionSet; + +UnionSet *init(int n) { // 并查集初始化 + UnionSet *u = (UnionSet *)malloc(sizeof(UnionSet)); // 初始化 + u->color = (int *)malloc(sizeof(int) * (n + 1)); // 从1号位开始存储 + u->n = n; + // 初始化颜色 + for (int i = 1; i <= n; i++) { + u->color[i] = i; + } + return u; +} + +int find(UnionSet *u, int x) { // 查找操作,查找x + return u->color[x]; // 返回当前元素的颜色 +} + +int merage(UnionSet *u, int a, int b) { // 合并操作, 合并a,b + if (find(u, a) == find(u, b)) return 0; // a,b本身就有连通关系 + // 染色 + int color_a = u->color[a]; + for (int i = 1; i <= u->n; i++) { + if (u->color[i] != color_a) continue; // if (u->color[i] - color_a) + u->color[i] = u->color[b]; // 找到和u->color[a] 一样的颜色 + } + return 1; +} + +void clear(UnionSet *u) { + if (u == NULL) return ; + free(u->color); + free(u); + return ; +} + + +int main() { + int n, m; + scanf("%d%d", &n, &m); // 分别代表人数和操作数。 + UnionSet *u = init(n); + for (int i = 0; i < m; i++) { + int a, b, c; + cin >> a >> b >> c; + switch (a) { + case 1:// 代表新增一条已知信息,𝑏,𝑐 是朋友 + merage(u, b, c); break; + case 2: // 代表根据以上信息,询问 𝑏,𝑐 是否是朋友 + cout << (find(u, b) == find(u, c) ? "Yes" : "No") << endl; + break; + } + } + + return 0; +} +``` + + + +## 6.4Quick-Union算法 + +Quick-Find 找代表元素 + +Quick-Union 找大哥 + + + +将6,5连通 + +![截屏2021-04-24 上午9.39.19](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-04-24%20%E4%B8%8A%E5%8D%889.39.19.png) + +![截屏2021-04-24 上午9.39.20](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-04-24%20%E4%B8%8A%E5%8D%889.39.20.png) + +![截屏2021-04-24 上午9.39.21](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-04-24%20%E4%B8%8A%E5%8D%889.39.21.png) + +![截屏2021-04-24 上午9.39.22](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-04-24%20%E4%B8%8A%E5%8D%889.39.22.png) + +![截屏2021-04-24 上午9.39.23](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-04-24%20%E4%B8%8A%E5%8D%889.39.23.png) + +![截屏2021-04-24 上午9.39.25](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-04-24%20%E4%B8%8A%E5%8D%889.39.25.png) + +![截屏2021-04-24 上午9.39.26](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-04-24%20%E4%B8%8A%E5%8D%889.39.26.png) + +![截屏2021-04-24 上午9.39.27](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-04-24%20%E4%B8%8A%E5%8D%889.39.27.png) + +![截屏2021-04-24 上午9.39.28](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-04-24%20%E4%B8%8A%E5%8D%889.39.28.png) + +![截屏2021-04-24 上午9.39.29](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-04-24%20%E4%B8%8A%E5%8D%889.39.29.png) + + + +## 6.5Quick-Union代码演示 + +``` + +``` + + + + + + + + + + + +![截屏2021-04-24 上午9.39.31](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-04-24%20%E4%B8%8A%E5%8D%889.39.31.png) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +------ + + + + + + diff --git "a/02.c++\347\254\224\350\256\260/04.\351\253\230\347\272\247\346\225\260\346\215\256\347\273\223\346\236\204.md" "b/02.c++\347\254\224\350\256\260/04.\351\253\230\347\272\247\346\225\260\346\215\256\347\273\223\346\236\204.md" new file mode 100644 index 0000000..2b33dc0 --- /dev/null +++ "b/02.c++\347\254\224\350\256\260/04.\351\253\230\347\272\247\346\225\260\346\215\256\347\273\223\346\236\204.md" @@ -0,0 +1,3932 @@ +[TOC] + + + + + +\--- + +title: 高级数据结构 + +date: 2020-12-22 08:08:15 + +tags: 高级数据结构 + +categories: 高级数据结构 + +\--- + + + + + +# 0、2020.12.22 + +------ + + + + + + + + + + + +# 1.BS二叉排序树O(logN) + +二叉排序树、二叉搜索树、二叉查找树 、Binary Search Tree + +数据结构定义一种性质,并且维护这种性质 + +## 1.性质 + +> 1.左子树 < 根节点 +> +> 2.根节点 < 右子树 +> +> 3.中序遍历的结果,是一个有序序列 + + + +> 用途:解决与排名相关的检索需求 + + + +> 树型结构:边代表关系,点代表集合 + + + + + + + +## 2.BS插入操作 + +> 1.插入新节点,一定会作为叶子节点 +> +> + +![截屏2020-12-19 下午6.39.38](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-12-19%20%E4%B8%8B%E5%8D%886.39.38.png) + +```c +Node *insert(Node *root, int val) { + if (root == NULL) return getNewNode(val); + if (root->data == val) return root; + if (val < root->data) root->lchild = insert(root->lchild, val); + else root->rchild = insert(root->rchild, val); + //update_size(root); + return root; +} +``` + + + +## 3.BS删除操作 + +> 1.删除叶子节点:直接删除, +> +> 2.删除度为1的节点:把孤儿树挂到父节点上面 +> +> (更新孩子节点的和父亲节点) +> +> 3.删除度为2的结点:找到前驱或者后继替换后转换为度为 0或1的结点问题 + + + +对于度为2 的节点: + +> 1.前驱:左子树中的最大值 +> +> 2.后继:右子树中的最小值 + +前驱没有右子树,后驱没有左子树,前驱和者后继度为0或1 + + + +节点查找次数的期望值: + +> 平均查找效率:总查找次数(1 + 2 * 节点数 + 3 * 节点数... )/总节点数(n) +> +> $\frac{总查找次数}{节点数}$ +> +> 假设每个结点等概率被查找 + + + + + + + +![截屏2020-12-19 下午11.15.08](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-12-19%20%E4%B8%8B%E5%8D%8811.15.08.png) + +![截屏2020-12-19 下午11.19.16](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-12-19%20%E4%B8%8B%E5%8D%8811.19.16.png) + + + +![截屏2020-12-21 下午4.46.47](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-12-21%20%E4%B8%8B%E5%8D%884.46.47.png) + + + +```c +Node *erase(Node *root, int value) { + if (root == NULL) return NULL; + if (value < root->data) { + root->lchild = erase(root->lchild, value); + } else if (value > root->data) { + root->lchild = erase(root->rchild, value); + } else {//找到结点 + /* if (root->lchild == NULL && root->rchild == NULL) { + free(root); + return NULL; + } else */ + if (root->lchild == NULL || root->rchild == NULL) { + Node *temp = root->lchild ? root->lchild : root->rchild; + free(root); + return temp;//返回删除节点的孩子节点,将孩子结点挂载到被删除节点的父亲节点上 + } else {//将两个结点的情况转换为一个结点 + Node *temp = predecessor(root); + root->data = temp->data; + root->lchild = erase(root->lchild, temp->data); + } + } + return root; +} +``` + + + +## 4.BS代码演示 + + + +```c++ +#include +#include + +#define KEY(n) (n ? n->data : 0) + +typedef struct Node { + int data; + struct Node *lchild, *rchild; +} Node; + +Node *getNewNode(int _data) {//初始化 + Node *p = (Node *)malloc(sizeof(Node)); + p->data = _data; + p->lchild = p->rchild = NULL; + return p; +} + +int search(Node *root, int value) { + if (root == NULL) return 0; + if (root->data == value) return 1; + if (value < root->data) return search(root->lchild, value); + return search(root->rchild, value); +} + +Node *insert(Node *root, int val) { + if (root == NULL) return getNewNode(val); + if (root->data == val) return root; + if (val < root->data) root->lchild = insert(root->lchild, val); + else root->rchild = insert(root->rchild, val); + return root; +} + +Node *predecessor(Node *root) {//前驱 + Node *temp = root->lchild; + while(temp && temp->rchild) temp = temp->rchild; + return temp; +} + + +Node *erase(Node *root, int value) { + if (root == NULL) return NULL; + if (value < root->data) { + root->lchild = erase(root->lchild, value); + } else if (value > root->data) { + root->lchild = erase(root->rchild, value); + } else {//找到结点 + /* if (root->lchild == NULL && root->rchild == NULL) { + free(root); + return NULL; + } else */ + if (root->lchild == NULL || root->rchild == NULL) { + Node *temp = root->lchild ? root->lchild : root->rchild; + free(root); + return temp;//返回删除节点的孩子节点,将孩子结点挂载到被删除节点的父亲节点上 + } else {//将两个结点的情况转换为一个结点 + Node *temp = predecessor(root); + root->data = temp->data; + root->lchild = erase(root->lchild, temp->data); + } + } + return root; +} + + + +void clear(Node *root) { + if (root == NULL) return ; + clear(root->lchild); + clear(root->rchild); + free(root); + return ; +} + +void output(Node *root) { + if (root == NULL) return ; + output(root->lchild); + printf("%d ", root->data); + output(root->rchild); + return ; +} + +void output1(Node *root) { + if (root == NULL) return ; + output1(root->lchild); + printf("(%d,%d,%d)\n", KEY(root), KEY(root->lchild), KEY(root->rchild)); + output1(root->rchild); + return ; +} + +int main() { + + int op, val; + Node *root = NULL;//!!!!!!!!!!!!!!! + while (~scanf("%d%d", &op, &val)) { + //0:查找 1:插入 2: 删除 + switch (op) { + case 0:printf("search %d, result : %d \n", val, search(root, val)); break; + case 1:{ + printf("insert: %d \n", val); + root = insert(root, val); + printf("\n"); + }break; + case 2:{ + printf("erase: %d \n", val); + root = erase(root, val); + printf("\n"); + } break; + } + + if (op) { + printf("\ntree ==> ["); + output(root); + printf("]\n"); + } + if (op) output1(root); + printf("\n------------------\n"); + } + clear(root); + + + return 0; +} +``` + + + + + +## 5.BS解决排名相关的需求 + +输出第k大的元素 + +1. 修改二叉排序树的结构定义,增加 size 字段,记录每棵树的节点数量 +2. $k - 1= LS$,根节点就是排名第 k 位的元素 +3. $k \le LS$,排名第 k 位的元素在左子树中 +4. $k \gt LS,search_k(root->rchild, k - LS - 1)$ + + + +```c +#include +#include + +#define KEY(n) (n ? n->data : 0) +#define SIZE(n) (n ? n->size : 0) + +typedef struct Node { + int data, size; + struct Node *lchild, *rchild; +} Node; + +Node *getNewNode(int _data) {//初始化 + Node *p = (Node *)malloc(sizeof(Node)); + p->data = _data; + p->size = 1; + p->lchild = p->rchild = NULL; + return p; +} + +int search(Node *root, int value) { + if (root == NULL) return 0; + if (root->data == value) return 1; + if (value < root->data) return search(root->lchild, value); + return search(root->rchild, value); +} + +int search_k(Node *root, int k) { + if (root == NULL) return -1; + if (SIZE(root->lchild) == k - 1) return root->data; + if (k <= SIZE(root->lchild)) { + return search_k(root->lchild, k); + } + return search_k(root->rchild, k - SIZE(root->lchild) - 1); +} + +void update_size(Node *root) { + root->size = SIZE(root->lchild) + SIZE(root->rchild) + 1; + return ; +} + +Node *insert(Node *root, int val) { + if (root == NULL) return getNewNode(val); + if (root->data == val) return root; + if (val < root->data) root->lchild = insert(root->lchild, val); + else root->rchild = insert(root->rchild, val); + update_size(root); + return root; +} + +Node *predecessor(Node *root) {//前驱 + Node *temp = root->lchild; + while(temp && temp->rchild) temp = temp->rchild; + return temp; +} + + +Node *erase(Node *root, int value) { + if (root == NULL) return NULL; + if (value < root->data) { + root->lchild = erase(root->lchild, value); + } else if (value > root->data) { + root->lchild = erase(root->rchild, value); + } else {//找到结点 + /* if (root->lchild == NULL && root->rchild == NULL) { + free(root); + return NULL; + } else */ + if (root->lchild == NULL || root->rchild == NULL) { + Node *temp = root->lchild ? root->lchild : root->rchild; + free(root); + return temp;//返回删除节点的孩子节点,将孩子结点挂载到被删除节点的父亲节点上 + } else {//将两个结点的情况转换为一个结点 + Node *temp = predecessor(root); + root->data = temp->data; + root->lchild = erase(root->lchild, temp->data); + } + } + update_size(root); + return root; +} + + + +void clear(Node *root) { + if (root == NULL) return ; + clear(root->lchild); + clear(root->rchild); + free(root); + return ; +} + +void output(Node *root) { + if (root == NULL) return ; + output(root->lchild); + printf("%d[%d] ", root->data, SIZE(root)); + output(root->rchild); + return ; +} + +void output1(Node *root) { + if (root == NULL) return ; + output1(root->lchild); + printf("(%d[%d],%d,%d)\n", KEY(root), SIZE(root), KEY(root->lchild), KEY(root->rchild)); + output1(root->rchild); + return ; +} + +int main() { + + int op, val; + Node *root = NULL; + while (~scanf("%d%d", &op, &val)) { + //0:查找 1:插入 2: 删除 + switch (op) { + case 0:printf("search %d, result : %d \n", val, search(root, val)); break; + case 1:{ + printf("insert: %d \n", val); + root = insert(root, val); + printf("\n"); + }break; + case 2:{ + printf("erase: %d \n", val); + root = erase(root, val); + printf("\n"); + } break; + case 3: { + printf("search k = %d, result : %d\n", + val, search_k(root, val)); + } break; + } + + if (op) { + printf("\ntree ==> ["); + output(root); + printf("]\n"); + } + if (op) output1(root); + printf("\n------------------\n"); + } + clear(root); + + + return 0; +} + +1 1 +insert: 1 + + +tree ==> [1[1] ] +(1[1],0,0) + +------------------ +1 2 +insert: 2 + + +tree ==> [1[2] 2[1] ] +(1[2],0,2) +(2[1],0,0) + +------------------ + 1 3 +insert: 3 + + +tree ==> [1[3] 2[2] 3[1] ] +(1[3],0,2) +(2[2],0,3) +(3[1],0,0) + +------------------ +1 4 +insert: 4 + + +tree ==> [1[4] 2[3] 3[2] 4[1] ] +(1[4],0,2) +(2[3],0,3) +(3[2],0,4) +(4[1],0,0) + +------------------ +1 5 +insert: 5 + + +tree ==> [1[5] 2[4] 3[3] 4[2] 5[1] ] +(1[5],0,2) +(2[4],0,3) +(3[3],0,4) +(4[2],0,5) +(5[1],0,0) + +------------------ +3 1 +search k = 1, result : 1 + +tree ==> [1[5] 2[4] 3[3] 4[2] 5[1] ] +(1[5],0,2) +(2[4],0,3) +(3[3],0,4) +(4[2],0,5) +(5[1],0,0) + +------------------ +3 2 +search k = 2, result : 2 + +tree ==> [1[5] 2[4] 3[3] 4[2] 5[1] ] +(1[5],0,2) +(2[4],0,3) +(3[3],0,4) +(4[2],0,5) +(5[1],0,0) + +------------------ +3 4 +search k = 4, result : 4 + +tree ==> [1[5] 2[4] 3[3] 4[2] 5[1] ] +(1[5],0,2) +(2[4],0,3) +(3[3],0,4) +(4[2],0,5) +(5[1],0,0) + +------------------ +``` + + + + + +## 6.BS解决Top-K 问题 + +(找到小于第 k 位的所有元素) + +1. 根节点就是第 k 位元素的话,就把左子树中的值全部输出出来 +2. 第 k 位在左子树中,前 k 位元素全都在左子树中 +3. 第 k 位在右子树中,说明根节点和左子树中的元素,都是前 k 位元素里面的值 + + + +所谓算法设计及分析能力:分类讨论及归纳总结的能力 + + + +```c +#include +#include + +#define KEY(n) (n ? n->data : 0) +#define SIZE(n) (n ? n->size : 0) +#define L(n) (n ? n->lchild : NULL) + +typedef struct Node { + int data, size; + struct Node *lchild, *rchild; +} Node; + +Node *getNewNode(int _data) {//初始化 + Node *p = (Node *)malloc(sizeof(Node)); + p->data = _data; + p->size = 1; + p->lchild = p->rchild = NULL; + return p; +} + +int search(Node *root, int value) { + if (root == NULL) return 0; + if (root->data == value) return 1; + if (value < root->data) return search(root->lchild, value); + return search(root->rchild, value); +} + +int search_k(Node *root, int k) { + if (root == NULL) return -1; + if (SIZE(root->lchild) == k - 1) return root->data; + if (k <= SIZE(root->lchild)) { + return search_k(root->lchild, k); + } + return search_k(root->rchild, k - SIZE(root->lchild) - 1); +} + +void update_size(Node *root) { + root->size = SIZE(root->lchild) + SIZE(root->rchild) + 1; + return ; +} + +Node *insert(Node *root, int val) { + if (root == NULL) return getNewNode(val); + if (root->data == val) return root; + if (val < root->data) root->lchild = insert(root->lchild, val); + else root->rchild = insert(root->rchild, val); + update_size(root); + return root; +} + +Node *predecessor(Node *root) {//前驱 + Node *temp = root->lchild; + while(temp->rchild) temp = temp->rchild; + return temp; +} + +Node *erase(Node *root, int value) { + if (root == NULL) return NULL; + if (value < root->data) { + root->lchild = erase(root->lchild, value); + } else if (value > root->data) { + root->rchild = erase(root->rchild, value); + } else {//找到结点 + /* if (root->lchild == NULL && root->rchild == NULL) { + free(root); + return NULL; + } else */ + if (root->lchild == NULL || root->rchild == NULL) { + Node *temp = root->lchild ? root->lchild : root->rchild; + free(root); + return temp;//返回删除节点的孩子节点,将孩子结点挂载到被删除节点的父亲节点上 + } else {//将两个结点的情况转换为一个结点 + Node *temp = predecessor(root); + root->data = temp->data; + root->lchild = erase(root->lchild, temp->data); + } + } + update_size(root); + return root; +} + + + +void clear(Node *root) { + if (root == NULL) return ; + clear(root->lchild); + clear(root->rchild); + free(root); + return ; +} + +void output(Node *root) { + if (root == NULL) return ; + output(root->lchild); + printf("%d[%d] ", root->data, SIZE(root)); + output(root->rchild); + return ; +} +void print(Node *root) { + printf("(%d[%d], %d, %d)\n", + KEY(root), SIZE(root), + KEY(root->lchild), KEY(root->rchild) + ); + return ; +} +void output1(Node *root) { + if (root == NULL) return ; + output1(root->lchild); + print(root); + output1(root->rchild); + return ; +} + +void output_k(Node *root, int k) { + if (k == 0 || root == NULL) return ; + if (k <= SIZE(root->lchild)) output_k(root->lchild, k); + else { + output1(root->lchild); + print(root); + output_k(root->rchild, k - SIZE(root->lchild) - 1); + } + return ; +} + +int main() { + + int op, val; + Node *root = NULL; + while (~scanf("%d%d", &op, &val)) { + //0:查找 1:插入 2: 删除 + switch (op) { + case 0:printf("search %d, result : %d \n", val, search(root, val)); break; + case 1:{ + printf("insert: %d \n", val); + root = insert(root, val); + printf("\n"); + }break; + case 2:{ + printf("erase: %d \n", val); + root = erase(root, val); + printf("\n"); + } break; + case 3: { + printf("search k = %d, result : %d\n", + val, search_k(root, val)); + } break; + case 4: { + printf("output top-%d elements\n", val); + output_k(root, val); + printf("------------\n"); + } break; + } + + if (op) { + printf("\ntree ==> ["); + output(root); + printf("]\n"); + } + if (op) output1(root); + printf("\n------------------\n"); + } + clear(root); + + + return 0; +} +``` + + + + + +## 7.二叉排序树和快排的关系 + + + +1. 二叉排序树是快速排序在思维逻辑结构层面用的数据结构 + +2. 思考1:快速排序的时间复杂度和二叉排序树建树时间复杂度之间的关系 + + + +3. 思考2:快速选择算法和二叉排序树之间的关系 + + + +4. 程序=算法+数据结构 + + + + + + + + + + + +# 2.AVL平衡二叉查找树O(logN) + +1962年(58) + +==**AVL树**== + +## 0.AVL树原理 + +> AVL树原理 +> +> 自平衡条件、旋转操作、旋转的触发 + + + +数据机构:定义一种性质并且维护一种性质 + + + +### 性质 + +1.左右子树高度差 小于等于 1 + +2.平衡二叉排序树,本质上也是二叉排序树,所以拥有二叉排序树的所有性质 + +3.平衡二叉排序树的学习重点:平衡条件以及平衡调整 + + + + + + + +> 高度为H的BS树所包含的结点数量在什么范围内: $H \le SIZE \le 2^H-1$ +> +> 高度为H的AVL树所包含的结点数量在什么范围内: $1.5^H \le SIZE \le 2^H-1$ +> +> $low(H) \le SIZE \le 2^H-1$ +> +> $low(H-1) + low(H - 2) + 1\le SIZE \le 2^H-1$(斐波那契数列) +> +> $1.5^H \le SIZE \le 2^H-1$ + +二叉树性质:二叉树第i层最多有2^i-1^个结点,深度为k最多有2^k^-1个结点 + +==>AVL节点数n和树高h的关系:h = logn + + + +思考: + +1. AVL 树改进的是节点数量的下限 +2. 树高 = 生命长度,节点数量 = 生命财富,不同的算法,达到的结果是不同的 +3. 教育提升的是底限,而不是上限,上限取决于能力和运气 + + + + + + + +## 1.自平衡条件 + +### 1.平衡因子 + +每个平衡因子的是指左子树最大高度和右子树最大高度差,平衡因子为-1,0,1被认为是平衡的 + +### 2.失衡类型 + +截屏2020-12-23 下午6.08.06 + +## 2.旋转操作:单旋和多旋 + +### 1.单旋 + +分为左旋和右旋。AVL树通过一系列的左旋和右旋操作,将不平衡的树调整为二叉查找树 + +#### 左旋: + +截屏2020-12-23 下午6.03.51 + +截屏2020-12-21 下午6.32.31 + + + +截屏2020-12-21 下午6.32.25 + + + +#### 右旋 + +截屏2020-12-23 下午6.03.57 + + + +截屏2020-12-21 下午6.37.45 + + + +截屏2020-12-21 下午6.37.41 + + + +### 2.多旋: + +由两次单旋操作组合而成 + +#### 1.左旋加右旋LR + +#### 截屏2020-12-24 下午12.38.14 + + + + + +截屏2020-12-23 下午6.10.27 + + + +26252FD93F9BE46395BB71A8E8888D4A + + + + + +截屏2020-12-21 下午6.45.05 + + + +截屏2020-12-21 下午6.45.01 + + + +截屏2020-12-21 下午6.45.10 + + + +#### 2.右旋加左旋RL + + + +截屏2020-12-21 下午6.47.47 + + + +截屏2020-12-21 下午6.47.42 + + + + + +截屏2020-12-21 下午6.47.52 + + + +#### 3. LL + +截屏2020-12-23 下午6.10.21 + + + +209777F2C379C9542061B8B56572755E + + + + + + + + + +#### 4.RR与LL对称 + + + +## 3.旋转的触发 + + + +插入操作: + +![截屏2020-12-21 下午6.58.33](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-12-21%20%E4%B8%8B%E5%8D%886.58.33.png) + + + +删除操作: + +![截屏2020-12-21 下午6.58.42](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-12-21%20%E4%B8%8B%E5%8D%886.58.42.png) + +> 平衡调整策略/方法 +> +> 1. 发生在回溯阶段的,第一个失衡节点处 +> 2. 理解平衡调整策略的关键在于:分析清楚四种情况下,ABCD 四棵子树树高的关系 +> 3. LL,大右旋 +> 4. LR,先小左旋,再大右旋 +> 5. RL,先小右旋,再大左旋 +> 6. RR,大左旋 + +截屏2020-12-23 下午6.22.17 + + + + + + + +## 4.代码实现 + +```c +#include +#include +#include + +typedef struct Node { + int data, height; + struct Node *lchild, *rchild; +} Node; + +Node __NIL;//假的空节点 +__attribute__((constructor)) +#define NIL (&__NIL) +void init_NIL() { + NIL->data = 0; + NIL->height = 0; + NIL->lchild = NIL->rchild = NIL; + return ; +} + +Node *getNewNode(int _data) { + Node *node = (Node *)malloc(sizeof(Node)); + node->data = _data; + node->lchild = node->rchild = NIL; + node->height = 1; + return node; +} + +void update_height(Node *root) { + root->height = (root->lchild->height > root->rchild->height ? root->lchild->height : root->rchild->height) + 1; + return ; +} + +Node *left_rotate(Node *root) { + Node *temp = root->rchild; + root->rchild = temp->lchild; + temp->lchild = root; + update_height(root); + update_height(temp); + return temp; +} + +Node *right_rotate(Node *root) {//对称 + Node *temp = root->lchild; + root->lchild = temp->rchild; + temp->rchild = root; + update_height(root); + update_height(temp); + return temp; +} + +Node *maintain(Node *root) { + if (abs(root->lchild->height - root->rchild->height) <= 1) return root; + if (root->lchild->height > root->rchild->height) {//L大左旋 + if (root->lchild->lchild->height < root->lchild->rchild->height)//LR小右旋 + root->lchild = left_rotate(root->lchild); + root = right_rotate(root); + } else { + if (root->rchild->rchild->height < root->lchild->rchild->height)//RL小左旋 + root->rchild = right_rotate(root->lchild);//小右旋 + root = left_rotate(root);//大左旋 + } + return root; +} + +Node *insert(Node *root, int val) { + if (root == NIL) return getNewNode(val); + if (root->data == val) return root;//不需要插入操作 + if (val < root->data) { + root->lchild = insert(root->lchild, val); + } else { + root->rchild = insert(root->rchild, val); + } + update_height(root); + + return maintain(root); +} + +Node *predecessor(Node *root) { + Node *temp = root->lchild; + while (temp && temp->lchild) temp = temp->rchild; + return temp; +} + +Node *erase(Node *root, int val) { + if (root == NIL) return NIL; + if (val < root->data) { + root->lchild = erase(root->lchild, val); + } else if (val > root->data) { + root->rchild = erase(root->rchild, val); + } else { + if (root->lchild == NIL || root->rchild == NIL) { + Node *temp = root->lchild != NIL ? root->lchild : root->rchild; + free(root); + return temp; + } else { + Node *temp = predecessor(root); + root->data = temp->data; + root->lchild = erase(root->lchild, temp->data); + } + } + update_height(root); + return maintain(root); +} + +void clear(Node *root) { + if (root == NIL) return ; + clear(root->lchild); + clear(root->rchild); + free(root); + return ; +} + +void output(Node *root) { + if (root == NIL) return ; + printf("(%d[%d],%d,%d)\n", + root->data, root->height, + root->lchild->data, + root->rchild->data); + output(root->lchild); + output(root->rchild); +} + +int main() { + int op, val; + Node *root = NIL; + while (~scanf("%d%d", &op, &val)) { + switch (op) { + case 0: root = erase(root, val); + case 1: + case 2: + case 3: root = insert(root, val); + } + output(root); + printf("--------------\n"); + } + clear(root); + + return 0; +} +``` + + + + + + + + + + + + + + + + + + + +# 3*.SBTree + +Size Balanced Tree + +## 1.平衡原理 + + + +![截屏2020-12-22 上午10.20.58](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-12-22%20%E4%B8%8A%E5%8D%8810.20.58.png) + + + +![截屏2020-12-22 上午10.21.30](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-12-22%20%E4%B8%8A%E5%8D%8810.21.30.png) + + + +![截屏2020-12-22 上午10.25.01](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-12-22%20%E4%B8%8A%E5%8D%8810.25.01.png) + + + + + +## 2.旋转操作 + + + +![截屏2020-12-22 上午10.26.35](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-12-22%20%E4%B8%8A%E5%8D%8810.26.35.png) + + + + + +![截屏2020-12-22 上午10.26.45](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-12-22%20%E4%B8%8A%E5%8D%8810.26.45.png) + + + +## 3.旋转的触发 + + + +![截屏2020-12-22 上午10.28.20](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-12-22%20%E4%B8%8A%E5%8D%8810.28.20.png) + + + + + +## 4.代码实现 + + + + + +```cpp +#include + +template +class SBTNode { +public: + Type data; + int size; + SBTNode *lchild, *rchild, *father; + SBTNode(Type init_data, int init_size = 0, SBTNode *init_father = NULL); + ~SBTNode(); + + void insert(Type value); + SBTNode* search(Type value); + SBTNode* predecessor(); + SBTNode* successor(); + void remove_node(SBTNode *delete_node); + bool remove(Type value); +}; + +template +class BinaryTree { +private: + SBTNode* root; +public: + BinaryTree(); + ~BinaryTree(); + void insert(Type value); + bool find(Type value); + bool remove(Type value); +}; + +SBTNode ZERO(0); +SBTNode* NIL = &ZERO; + +template +SBTNode::SBTNode(Type init_data, int init_size, SBTNode* init_father) { + data = init_data; + size = init_size; + lchild = NIL; + rchild = NIL; + father = init_father; +} + +template +SBTNode::~SBTNode() { + if (lchild != NIL) { + delete lchild; + } + if (rchild != NIL) { + delete rchild; + } +} + +template +SBTNode* left_rotate(SBTNode* node) { + SBTNode *temp = node->rchild; + node->rchild = temp->lchild; + temp->lchild->father = node; + temp->lchild = node; + temp->father = node->father; + node->father = temp; + temp->size = node->size; + node->size = node->lchild->size + node->rchild->size + 1; + return temp; +} + +template +SBTNode* right_rotate(SBTNode* node) { + SBTNode *temp = node->lchild; + node->lchild = temp->rchild; + temp->rchild->father = node; + temp->rchild = node; + temp->father = node->father; + node->father = temp; + temp->size = node->size; + node->size = node->lchild->size + node->rchild->size + 1; + return temp; +} + +template +SBTNode* maintain(SBTNode* node, bool flag) { +} + +template +SBTNode* insert(SBTNode* node, Type value) { + node->size++; + if (value > node->data) { + if (node->rchild == NIL) { + node->rchild = new SBTNode(value, 1, node); + } else { + node->rchild = insert(node->rchild, value); + } + } else { + if (node->lchild == NIL) { + node->lchild = new SBTNode(value, 1, node); + } else { + node->lchild = insert(node->lchild, value); + } + } + return maintain(node, value > node->data); +} + +template +SBTNode* SBTNode::search(Type value) { + if (data == value) { + return this; + } else if (value > data) { + if (rchild == NIL) { + return NIL; + } else { + return rchild->search(value); + } + } else { + if (lchild == NIL) { + return NIL; + } else { + return lchild->search(value); + } + } +} + +template +SBTNode* SBTNode::predecessor() { + SBTNode* temp = lchild; + while (temp != NIL && temp->rchild != NIL) { + temp = temp->rchild; + } + return temp; +} + +template +SBTNode* SBTNode::successor() { + SBTNode* temp = rchild; + while (temp != NIL && temp->lchild != NIL) { + temp = temp->lchild; + } + return temp; +} + +template +void SBTNode::remove_node(SBTNode* delete_node) { + SBTNode* temp = NIL; + if (delete_node->lchild != NIL) { + temp = delete_node->lchild; + temp->father = delete_node->father; + delete_node->lchild = NIL; + } + + if (delete_node->rchild != NIL) { + temp = delete_node->rchild; + temp->father = delete_node->father; + delete_node->rchild = NIL; + } + if (delete_node->father->lchild == delete_node) { + delete_node->father->lchild = temp; + } else { + delete_node->father->rchild = temp; + } + temp = delete_node; + while (temp != NULL) { + temp->size--; + temp = temp->father; + } + delete delete_node; +} + +template +bool SBTNode::remove(Type value) { + SBTNode *delete_node, *current_node; + current_node = search(value); + if (current_node == NIL) { + return false; + } + if (current_node->lchild != NIL) { + delete_node = current_node->predecessor(); + } else if (current_node->rchild != NIL) { + delete_node = current_node->successor(); + } else { + delete_node = current_node; + } + current_node->data = delete_node->data; + remove_node(delete_node); + return true; +} + +template +BinaryTree::BinaryTree() { + root = NULL; +} + +template +BinaryTree::~BinaryTree() { + delete root; +} + +template +void BinaryTree::insert(Type value) { + if (root == NULL) { + root = new SBTNode(value, 1); + } else if (root->search(value) == NIL) { + root = ::insert(root, value); + } +} + +template +bool BinaryTree::find(Type value) { + if (root->search(value) == NIL) { + return false; + } else { + return true; + } +} + +template +bool BinaryTree::remove(Type value) { + return root->remove(value); +} + +int main() { + return 0; +} +``` + + + + + + + + + + + + + +# 4.红黑树O(logN) + +## 1.平衡条件 + +1. 节点非黑既红 +2. 根节点是黑色 +3. 叶子(NIL)结点是黑色 +4. 如果一个节点是红色,则它的两个子节点都是黑色 +5. 从根节点到叶子结点路径上,黑色节点数量相同 + +==>最长边和最短边之间的关系:最长边是最短边的二倍 + +截屏2020-12-23 下午9.44.56 + + + + + +> 第4条和第5条条件,注定了,红黑树中最长路径是最短路径的长度的 2 倍。 +> +> 本质上,红黑树也是通过树高来控制平衡的。 +> +> 红黑树比 AVL 树树高控制条件要更松散,红黑树在发生节点插入和删除以后,发生调整的概率,比 AVL 树要更小。 + + + + + +## 2.平衡调整策略 + +1.插入调整站在祖父节点看 + +2.删除调整站在父亲节点看 + +3.插入和删除的情况处理,一共五种 + + + +> 理解 +> +> 1. 理解红黑树的插入调整,要站在==祖父节点==向下进行调整 +> 2. 理解红黑树的删除调整,要站在==父节点==向下进行调整 +> 3. 插入调整,主要就是为了解决双红情况 +> 4. 新插入的节点一定是红色,插入黑色节点一定会产生冲突,违反条件5,插入红色节点,不一定产生冲突 +> 5. 把每一种情况,想象成一棵大的红黑树中的局部子树 +> 6. 局部调整的时候,为了不影响全局,调整前后的路径上黑色节点数量相同 + + + +## 3.插入方法 + +​ + +1.叔叔节点为红色的时候,修改三元组小帽子,改成红黑黑(4种情况) + +x有4个位置,4种情况,但是处理方法一样 + +根节点必定为黑色,否则插入前已经冲突 + +eg:1,20修改成黑色,15修改成红色(红色上顶) + +![截屏2020-12-24 上午11.16.48](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-12-24%20%E4%B8%8A%E5%8D%8811.16.48.png) + +2.叔叔节点为黑色的时候,有四种情况LL,LR,RL,RR + +参考 AVL 树的失衡情况,分成 $LL,LR,RL,RR$, 先参考 AVL 树的旋转调整策略,然后再修改三元组的颜色,有两种调整策略:红色上浮,红色下沉。 + +eg:LL型进行大右旋,**20**调整成红色,**15**调整成⿊色,即可搞定问题(红色上浮), + +或者调整10为黑色,红色上浮 + +截屏2020-12-24 上午11.17.02 + + + +截屏2020-12-24 上午11.29.58 + +3.两大类情况,包含 8 种小情况 + + + +## 4.插入代码演示 + +1. 插入调整,发正在递归的回溯阶段 +2. 插入调整代码中,使用 goto 语句,8行代码,变成了4行 +3. 处理根节点一定是黑色,通过代码封装,$insert->\_\_insert$ + + + +```cpp +#include +#include + +typedef struct Node { + int data; + int color;//0 red, 1 black 2double black + struct Node *lchild, *rchild; +} Node; + +Node __NIL; +#define NIL (&__NIL) +__attribute__((constructor)) +void init_NIL() { + NIL->data = 0; + NIL->color = 1; + NIL->lchild = NIL->rchild = NIL; + return ; +} + +Node *getNewNode(int _data) { + Node *root = (Node *)malloc(sizeof(Node)); + root->data = _data; + root->color = 0; + root->lchild = root->rchild = NIL; + return root; +} + +int has_red_child(Node *root) {//判断根节点是否有红色的子孩子 + return root->lchild->color == 0 || root->rchild->color == 0; +} + +Node *left_rotate(Node *root) { + Node *temp = root->rchild; + root->rchild = temp->lchild; + temp->lchild = root; + return temp; +} + +Node *right_rotate(Node *root) { + Node *temp = root->lchild; + root->lchild = temp->rchild; + temp->rchild = root; + return temp; +} + +Node *insert_maintain(Node *root) { + if (!has_red_child(root)) return root;//没有红色的孩子,不需要调整 + //没有判断是否发生双红冲突 + //不冲突的话,更改颜色不影响结果,如果发生冲突,更改颜色,解决冲突,root必定为黑色,因为孩子为红色 + int flag = 0; + if (root->lchild->color == 0 && root->rchild->color == 0) goto insert_end; + //{ + // root->color = 0; + // root->lchild->color = root->rchild->color = 1; + //return root; + //} + if (root->lchild->color == 0 && has_red_child(root->lchild)) flag = 1; + if (root->rchild->color == 0 && has_red_child(root->rchild)) flag = 2; + if (flag == 0) return root; + if (flag == 1) { + if (root->lchild->rchild->color == 0)//LR,需要小左旋,大右旋 + root->lchild = left_rotate(root->lchild); + root = right_rotate(root); + //root->color = 0;//红色上浮,红色下沉也可 + // root->lchild->color = root->rchild->color = 1; + // return root; + } else {// + if (root->rchild->lchild->color == 0) { + root->rchild = right_rotate(root->rchild); + } + root = left_rotate(root); + //root->color = 0; + // root->lchild->color = root->rchild->color = 1; + } +insert_end: + root->color = 0;//红色上浮,红色下沉也可 + root->lchild->color = root->rchild->color = 1; + return root; +} + +Node *__insert(Node *root, int value) { + if (root == NIL) return getNewNode(value); + if (root->data == value) return root; + if (value < root->data) root->lchild = __insert(root->lchild, value); + else root->rchild = __insert(root->rchild, value); + return insert_maintain(root); +} + +Node *insert(Node *root, int data) { + root = __insert(root, data); + root->color = 1; + return root; +} + +void clear(Node *root) { + if (root == NIL) return ; + clear(root->lchild); + clear(root->rchild); + free(root); + return ; +} + +void print(Node *root) { + printf("(%d| %d, %d, %d)\n", + root->color, root->data, + root->lchild->data, + root->rchild->data + ); + return ; +} + +void output(Node *root) { + if (root == NIL) return ; + print(root); + output(root->lchild); + output(root->rchild); + return ; +} + +int main() { + int op, val; + Node *root = NIL; + while (~scanf("%d%d", &op, &val)) { + switch (op) { + case 1: root = insert(root, val); break; + } + output(root); + printf("------------\n"); + } + + + return 0; +} +``` + + + +```cpp +1 1 +(1| 1, 0, 0) +------------ +1 2 +(1| 1, 0, 2) +(0| 2, 0, 0) +------------ +1 3 +(1| 2, 1, 3) +(1| 1, 0, 0) +(1| 3, 0, 0) +------------ +1 4 +(1| 2, 1, 3) +(1| 1, 0, 0) +(1| 3, 0, 4) +(0| 4, 0, 0) +------------ +``` + + + +``` +1 -60 +2 -95 +1 47 +1 -65 +1 18 +3 -53 +``` + + + + + +## 5.删除调整的触发 + +1. 删除红色节点,不会对红黑树的平衡产生影响 +2. 度为1的黑色节点,唯一子孩子一定是红色,因为到叶子结点的黑色节点相同,如果为黑色则会违背平衡条件 +3. 所以删除度为1的黑色节点,不会产生删除调整, +4. 删除度为0的黑色节点,会产生一个双重黑的 NIL 节点(color=2) +5. 所以删除调整的关键,就是为了干掉双重黑 + +==>双重黑结点触发删除调整 + + + +## 7.删除调整分类 + +> 1.双重黑节点的兄弟节点是黑色,兄弟节点下面的两个子节点也是黑色, + +调整方法:父节点增加一重黑色,双重黑与兄弟节点,分别减少一重黑色。 + +情况一 + +图一 + +> 2.双黑结点的兄弟节点是黑色,并且,兄弟节点中有==红色子节点==,有四种情况 + + 1).R(兄弟)==R==(右子节点),==左==旋,新根结点改成原根结点的颜色,将新根的两个子节点,改成黑色,双重黑改成一重黑 + +情况三(双黑左旋之后被干掉)兄弟的右子树结点为红色,左子树结点不一定 + +截屏2020-12-24 下午4.29.06 + +截屏2020-12-24 下午5.04.12 + + + + + +2).R(兄弟)==L==(左子节点)(红色),先小==右==旋,对调新根与原根的颜色,转成上一种情况(RL→RR) + +(情况二)RL,兄弟右子树一定为黑色,左子树为红色 + +截屏2020-12-24 下午11.02.27 + + + +截屏2020-12-24 下午10.06.55 + +3).LL 同理 RR + +4).LR 同理 RL + +> 4.兄弟节点是红色,通过旋转,转变成兄弟节点是黑色的情况 + + 左旋,原根节点改红,新根节点改黑,双黑-1 + +截屏2020-12-24 下午10.22.04 + + + +截屏2020-12-24 下午10.25.28 + + + + + + + +## 6.删除代码实现 + +进行 LR/RL 类型判断的时候,不能判断 LL 子树是否为黑色,LL 子树有可能是 NIL 节点,在某些特殊情况下,读到的颜色可能是双重黑,取而代之的判断方法就是【LL 子树不是红色】。 + + + +```cpp +#include +#include + +typedef struct Node { + int data; + int color;//0 red, 1 black 2double black + struct Node *lchild, *rchild; +} Node; + +Node __NIL; +#define NIL (&__NIL) +__attribute__((constructor)) +void init_NIL() { + NIL->data = 0; + NIL->color = 1; + NIL->lchild = NIL->rchild = NIL; + return ; +} + +Node *getNewNode(int _data) { + Node *root = (Node *)malloc(sizeof(Node)); + root->data = _data; + root->color = 0; + root->lchild = root->rchild = NIL; + return root; +} + +int has_red_child(Node *root) {//判断根节点是否有红色的孩子结点 + return root->lchild->color == 0 || root->rchild->color == 0; +} + +Node *left_rotate(Node *root) { + Node *temp = root->rchild; + root->rchild = temp->lchild; + temp->lchild = root; + return temp; +} + +Node *right_rotate(Node *root) { + Node *temp = root->lchild; + root->lchild = temp->rchild; + temp->rchild = root; + return temp; +} + +Node *insert_maintain(Node *root) { + if (!has_red_child(root)) return root;//没有红色的孩子,不需要调整 + int flag = 0; + if (root->lchild->color == 0 && root->rchild->color == 0) goto insert_end; + if (root->lchild->color == 0 && has_red_child(root->lchild)) flag = 1; + if (root->rchild->color == 0 && has_red_child(root->rchild)) flag = 2; + if (flag == 0) return root; + if (flag == 1) { + if (root->lchild->rchild->color == 0)//LR,需要小左旋,大右旋 + root->lchild = left_rotate(root->lchild); + root = right_rotate(root); + } else {// + if (root->rchild->lchild->color == 0) { + root->rchild = right_rotate(root->rchild); + } + root = left_rotate(root); + } +insert_end: + root->color = 0;//红色上浮,红色下沉也可,这里选择红色上浮 + root->lchild->color = root->rchild->color = 1; + return root; +} + +Node *__insert(Node *root, int value) { + if (root == NIL) return getNewNode(value); + if (root->data == value) return root; + if (value < root->data) root->lchild = __insert(root->lchild, value); + else root->rchild = __insert(root->rchild, value); + return insert_maintain(root); +} + +Node *insert(Node *root, int data) { + root = __insert(root, data); + root->color = 1; + return root; +} + +Node *predecessor(Node *root) { + Node *temp = root->lchild; + while (temp->rchild != NIL) temp = temp->rchild; + return temp; +} + +Node *erase_maintain(Node *root) { + if (root->lchild->color != 2 && root->rchild->color != 2) return root; + if (has_red_child(root)) {//兄弟节点是红色,通过旋转,转变成兄弟节点是黑色的情况 + int flag = 0;//1:右旋,2:左旋 + root->color = 0;//原根节点改红 + if (root->lchild->color == 0) {//左孩子为红色,右孩子为双黑结点,减掉一重黑色,右旋 + root->rchild->color -= 1; + root = right_rotate(root); + flag = 1; + } + else {//右孩子为红色,左孩子为双黑结点,双黑结点减掉一重黑色,左旋 + root->lchild->color -= 1; + root = left_rotate(root); + flag = 2; + } + root->color = 1;//新根节点改黑 + if (flag == 1) root->rchild = erase_maintain(root->rchild);//若发生右旋,已经转变成兄弟结点为黑色的情况,递归检查右边是否平衡, + else root->lchild = erase_maintain(root->lchild);//检查左边是否平衡 + return root; + } + if ((root->lchild->color == 2 && !has_red_child(root->rchild)) || + (root->rchild->color == 2 && !has_red_child(root->lchild)) ) { + //双重黑节点的兄弟节点是黑色,兄弟节点下面的两个子节点也是黑色(即无红色) + root->lchild->color -= 1;//父节点增加一重黑色,双重黑与兄弟节点,分别减少一重黑色 + root->rchild->color -= 1; + root->color += 1; + return root; + } + //双黑结点的兄弟节点是黑色,并且,兄弟节点中有红色子节点 + //R(兄弟)R(右子节点),左旋,新根结点改成原根结点的颜色,将新根的两个子节点,改成黑色,双重黑减1 + //R(兄弟)L(左子节点)(红色),先小右旋,对调新根与原根的颜色,转成RR(RL→RR) + if (root->lchild->color == 2) {//左兄弟为双黑结点 + if (root->rchild->rchild->color != 0) {//---- != root->rchild->rchild->color == 1(NIL) + //------------不是红色 + root->rchild->color = 0; + root->rchild = right_rotate(root->rchild); + root->rchild->color = 1; + + } + root = left_rotate(root);//大左旋 + root->color = root->lchild->color;//新根结点改成原根结点的颜色 + } else {//右兄弟结点为双黑结点 + if (root->lchild->lchild->color != 0) { + root->lchild->color = 0; + root->lchild = left_rotate(root->lchild); + root->lchild->color = 1; + root->lchild->color -= 1; + + } + root = right_rotate(root);//大右旋 + root->color = root->rchild->color; + } + root->lchild->color = root->rchild->color = 1;// + return root; +} + +Node *__erase(Node *root, int value) { + if (root == NIL) return NIL; + if (value < root->data) root->lchild = __erase(root->lchild, value); + else if (value > root->data) root->rchild = __erase(root->rchild, value); + else { + if (root->lchild == NIL || root->rchild == NIL) { + Node *temp = root->lchild != NIL ? root->lchild : root->rchild; + temp->color += root->color; + //若根节点为红色,值不变,若为黑色,子孩子一定为红色, + //temp == NIL,root度为0,则,color=2,产生双黑结点 + free(root); + return temp; + } else { + Node *temp = predecessor(root); + root->data = temp->data; + root->lchild = __erase(root->lchild, temp->data); + } + } + return erase_maintain(root); +} + +Node *erase(Node *root, int value) { + root = __erase(root, value); + root->color = 1; + return root; +} + +void clear(Node *root) { + if (root == NIL) return ; + clear(root->lchild); + clear(root->rchild); + free(root); + return ; +} + +void print(Node *root) { + printf("(%d| %d, %d, %d)\n", + root->color, root->data, + root->lchild->data, + root->rchild->data + ); + return ; +} + +void output(Node *root) { + if (root == NIL) return ; + print(root); + output(root->lchild); + output(root->rchild); + return ; +} + +int main() { + int op, val; + Node *root = NIL; + while (~scanf("%d%d", &op, &val)) { + switch (op) { + case 1: root = insert(root, val); break; + case 2: root = erase(root, val); break; + case 3: output(root); + } + output(root); + printf("------------\n"); + } + + + return 0; +} +``` + + + + + +```cpp +1 1 +(1| 1, 0, 0) +------------ +1 2 +(1| 1, 0, 2) +(0| 2, 0, 0) +------------ +1 3 +(1| 2, 1, 3) +(1| 1, 0, 0) +(1| 3, 0, 0) +------------ +2 3 +(1| 2, 1, 0) +(0| 1, 0, 0) +------------ +``` + + + +```bash +1 1 +(1| 1, 0, 0) +------------ +1 2 +(1| 1, 0, 2) +(0| 2, 0, 0) +------------ +1 3 +(1| 2, 1, 3) +(1| 1, 0, 0) +(1| 3, 0, 0) +------------ +1 -5 +(1| 2, 1, 3) +(1| 1, -5, 0) +(0| -5, 0, 0) +(1| 3, 0, 0) +------------ +2 3 +(1| 1, -5, 2) +(1| -5, 0, 0) +(1| 2, 0, 0) +------------ +``` + + + +截屏2020-12-25 上午1.47.12 + + + +截屏2020-12-25 上午1.49.37 + + + + + +## 7.红黑树代码实现 + +![截屏2021-01-05 下午4.53.06](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-01-05%20%E4%B8%8B%E5%8D%884.53.06.png) + +```c +#include +#include + +typedef struct Node { + int data; + int color;//0 red, 1 black 2double black + struct Node *lchild, *rchild; +} Node; + +Node __NIL; +#define NIL (&__NIL) +__attribute__((constructor)) +void init_NIL() {//空节点 + NIL->data = 0; + NIL->color = 1; + NIL->lchild = NIL->rchild = NIL; + return ; +} + +Node *getNewNode(int data) {//初始化一个新节点 + Node *root = (Node *)malloc(sizeof(Node)); + root->data = data; + root->color = 0; + root->lchild = root->rchild = NIL; + return root; +} + +int has_red_child(Node *root) {//判断根节点的孩子结点是否有红色 + return root->lchild->color == 0 || root->rchild->color == 0; +} + +Node *left_rotate(Node *root) { + //左旋,旧根节点的右孩子变成根节点,旧根节点的右孩子变成新根节点的左孩子,新根节点左孩子变成旧根节点 + Node *temp = root->rchild;//新根节点 + root->rchild = temp->lchild;//旧根节点的右孩子变成新根节点的左孩子 + temp->lchild = root;//新根节点左孩子是旧根节点 + return temp; +} + +Node *right_rotate(Node *root) { + //右旋,旧根节点左孩子变成新根节点,旧根节点的左孩子变成新根节点的右孩子,新根节点的右孩子变成旧根节点 + Node *temp = root->lchild;//新根节点为旧根节点的左孩子 + root->lchild = temp->rchild;//旧根节点的左孩子变成新根节点的右孩子 + temp->rchild = root;//新根节点的右孩子变成旧根节点 + return temp; +} + +Node *insert_maintain(Node *root) {//插入新节点 + if (!has_red_child(root)) return root;//没有红色的孩子,不需要调整 + //没有判断是否发生双红冲突 + //不冲突的话,更改颜色不影响结果,如果发生冲突,更改颜色,解决冲突,root必定为黑色,因为孩子为红色 + int flag = 0; + //分为两种情况 1.根节点的孩子中都是红色结点,2.根节点的孩子中有一个是红色 + // 1.根节点的孩子中都是红色结点 + if (root->lchild->color == 0 && root->rchild->color == 0 && + (has_red_child(root->lchild) || has_red_child(root->rchild))) goto insert_end; + // 2.根节点的孩子中有一个是红色,使用flag标记两种情况 + // 2.1 根节点的左孩子是红色,且左孩子的子树中有红色结点 + if (root->lchild->color == 0 && has_red_child(root->lchild)) flag = 1; + // 2.2 根结点的右孩子是红色,且右孩子的子树中有红色结点 + if (root->rchild->color == 0 && has_red_child(root->rchild)) flag = 2; + if (flag == 0) return root; + if (flag == 1) { + // 2.1 根节点的左孩子是红色,且左孩子的子树中有红色结点 + // 2.1.1 根节点的左孩子的右孩子为红色,为LR型,需要小左旋大右旋, + // 2.1.2 根节点的左孩子的左孩子为红色,为LL型,需要大右旋, + if (root->lchild->rchild->color == 0)//LR,需要小左旋,大右旋 + root->lchild = left_rotate(root->lchild); + root = right_rotate(root); + } else { + // 2.2 根结点的右孩子是红色,且右孩子的子树中有红色结点 + // 2.2.1 根节点的右孩子的左孩子为红色,为RL型,需要小右旋大左旋, + // 2.2.2 根节点的左孩子的左孩子为红色,为RR型,需要大右旋, + if (root->rchild->lchild->color == 0) {//RL,需要小右旋,大左旋 + root->rchild = right_rotate(root->rchild); + } + root = left_rotate(root); + } +insert_end: + root->color = 0;//这里选择红色上浮,其实红色上浮,红色下沉均可, + root->lchild->color = root->rchild->color = 1; + return root; +} + + +Node *__insert(Node *root, int value) { + if (root == NIL) return getNewNode(value);//根节点为空或者找到插入位置,插入新节点 + if (root->data == value) return root;//新插入的结点已经存在,不需要操作 + //在左子树中插入 + if (value < root->data) root->lchild = __insert(root->lchild, value); + //在右子树中插入 + else root->rchild = __insert(root->rchild, value); + //插入完之后进行平衡调整,然后 返回结果 + return insert_maintain(root); +} + +Node *insert(Node *root, int value) {//插入 + root = __insert(root, value); + root->color = 1; + return root; +} + +Node *predecessor(Node *root) { + Node *temp = root->lchild; + while (temp->rchild != NIL) temp = temp->rchild; + return temp; +} + +Node *erase_maintain(Node *root) { + if (root->rchild->color != 2 && root->lchild->color != 2) return root; + if (!has_red_child(root)) {//1.双黑兄弟无红色 + if (root->lchild->color == 2) {//1.2左双黑结点 + if (!has_red_child(root->rchild)) {//1.2.1双黑结点在左,且右兄弟子结点无红色 + //父结点增加1重黑,双重黑结点与兄弟结点减少1重黑 + root->color += 1; + root->lchild->color -=1; + root->rchild->color -=1; + } else {//1.2.2双黑结点在左,且右兄弟有红色子结点,分为RL,RR + root->lchild->color = 1; + if (root->rchild->rchild->color != 0) {// 1.2.2.3右兄弟结点,其右孩子不为红色,左孩子为红色,RL + //小右旋,原兄弟结点改成红色,新兄弟结点改成黑色,转变成RR型 + //root->rchild->lchild->color = root->rchild->color;//=1改黑** + root->rchild->color = 0;//改红 + root->rchild = right_rotate(root->rchild); + root->rchild->color = 1;//改黑** + } + //大左旋,新根结点等于原根结点 [双黑结点的父结点] 的颜色,两个新的子结点改为黑色,双黑结点减少1重黑 + root->rchild->color = root->color; + root = left_rotate(root); + root->rchild->color = root->lchild->color = 1; + } + } else if (root->rchild->color == 2){//1.3右双黑结点 + if (!has_red_child(root->lchild)) {//1.3.1双黑结点在右,且左兄弟子结点无红色, + //父结点增加1重黑,双重黑结点与兄弟结点减少1重黑 + root->color += 1; + root->lchild->color -=1; + root->rchild->color -=1; + } else {//1.3.2双黑结点在右,且左兄弟有红色子结点,分为LR,LL + root->rchild->color = 1;//双重黑减一重黑 + if (root->lchild->lchild->color != 0) {// 1.3.2.3左兄弟结点,其右孩子不为红色,即左孩子为红色,LL + //小左旋,原兄弟结点改成红色,新兄弟结点改成黑色,转变成LL型 + //root->lchild->rchild->color = root->lchild->color;//=1 + root->lchild->color = 0;//改红 + root->lchild = left_rotate(root->lchild); + root->lchild->color = 1;//改黑 + } + //大右旋,新根结点等于原根结点 [双黑结点的父结点] 的颜色,两个新的子结点改为黑色,双黑结点减少1重黑 + root->lchild->color = root->color; + root = right_rotate(root); + root->rchild->color = root->lchild->color = 1; + } + } + } else { + //2.双黑兄弟结点有红色,抓着双黑结点的父结点,向双黑结点旋转,原根结点改为红色,新根结点改为黑色 + if (root->lchild->color == 0) {//2.2右双黑,左为红,向右旋转,原根结点改为红色,新根结点改为黑色 + //root->lchild->color = root->color; + root->color = 0; + root = right_rotate(root); + root->color = 1; + root->rchild = erase_maintain(root->rchild); + } else {//2.3左双黑,右为红,向左旋转,原根结点改为红色,新根结点改为黑色 + //root->rchild->color = root->color; + root->color = 0; + root = left_rotate(root); + root->color = 1; + root->lchild = erase_maintain(root->lchild); + } + } + return root; +} + +Node *__erase(Node *root, int value) { + //删除有两种情况,删除度为2,和删除度为0和1 + if (root == NIL) return NIL;//没有要删除的值 + if (value < root->data) root->lchild = __erase(root->lchild, value);// 要删除的值在左子树中 + else if (value > root->data) root->rchild = __erase(root->rchild, value);// 要删除的值在右子树中 + else {// 找到要删除的值,有4种情况 + // 1.要删除的结点的孩子结点有空节点,即删除度为1或者度为0的结点 + if (root->lchild == NIL || root->rchild == NIL) { + Node *temp = root->lchild != NIL ? root->lchild : root->rchild; + temp->color += root->color; + // 1.1删除度为1的结点,分两种情况 + // 1.1.1若删除的根节点为红色,不会对红黑树的平衡产生影响, + // 1.1.2若删除的根节点为黑色,他的子孩子一定为红色,如果为黑色,树不会平衡;删除根节点为黑色,temp->color = 2;产生双重黑结点 + // 1.2删除度为0的结点,分两种情况 + // 1.2.1删除度为0的红色结点,不会产生影响 + // 1.2.2删除度为0的黑色结点,temp = NIL,color=2,产生双黑结点 + free(root);//释放要删除的结点 + return temp; + // 2.删除度为2的结点,转换成度为1或0的结点,转换的方法是和他的前驱结点交换 + } else { + Node *temp = predecessor(root); + root->data = temp->data; + root->lchild = __erase(root->lchild, temp->data); + } + } + root = erase_maintain(root); + return root; +} + +Node *erase(Node *root, int value) {//删除结点 + root = __erase(root, value); + root->color = 1;//根节点为黑色 + return root; +} + +void clear(Node *root) { + if (root == NIL) return ; + clear(root->lchild); + clear(root->rchild); + free(root); + return ; +} + +void print(Node *root) { + printf("%d %d %d %d\n", + root->data, + root->color, + root->lchild->data, + root->rchild->data + ); + return ; +} + +void output(Node *root) {//中序遍历 + if (root == NIL) return ; + output(root->lchild); + print(root); + output(root->rchild); + return ; +} + +int main() { + int op, val; + Node *root = NIL; + while (~scanf("%d%d", &op, &val)) { + switch (op) { + case 1: root = insert(root, val); break; + case 2: root = erase(root, val); break; + case 3: output(root); + } + //output(root); + //printf("------------\n"); + } + + + return 0; +} +``` + + + + + + + + + + + + + + + + + + + +# 5.哈夫曼树 + +## 1.编码 + + + +### 什么是编码 + + + +'a' = 97 = $(0110\ 0001)_2$ + +'0' = 48 = $(0011\ 0000)_2$ + +注意:任何信息,在计算机中,都是二进制存储的 + + + +信息:"aa00" = $01100001、01100001、00110000、00110000$ + +一台计算机 传输到 另外一台计算机,传输 32 个比特位 + +假设:计算机的网络是 32bit/s。所以用时:1 s + + + +特定场静:只有 a,b,0,1 四种字符需要传输 + +海贼班编码:a:00, b: 01, 0: 10, 1: 11 + +"aa00" = 00001010 + +在带宽不变的情况下,当前只需要传输 0.25s + + + + + +### 定长与变长编码 + + + +2. Ascii编码和特定场景下的编码,都属于定长编码 +3. 对于每一个字符,编码长度相同,这就是定长编码 +4. UTF-8编码是变长编码,UTF-16是定长编码 +5. 对于每一个字符,编码长度不同,这就是变长编码 +5. 将定长编码,看成是变长编码的特例 +6. 变长编码,一定不差于定长编码 + + + + + +### 变长编码应用场景 + + + +特定场静: + +1. 只有四种字符 : ab01 +2. a: 0.8, b: 0.05, 0: 0.1, 1: 0.05 + +平均编码长度: + +$l_i$:第 i 种字符,编码长度 + +$p_i$:第 i 种字符,出现概率 + +$avg(l) = \sum{l_i}\times{p_i}$ + + + +假设,平均编码长度:1.16,估算传输100个字符,需要传输116个比特位 + + + +海贼班编码的平均编码长度:$avg(l) = 2\times\sum{p_i}=2$ + +平均编码长度就是编码的定长 + + + +新·海贼班编码: + +a: 1 + +b: 01 + +0: 000 + +1: 001 + + + +平均编码长度:$1*0.8+2*0.05+3*0.1+3*0.05=1.35$ + + + +100个字符,传输135个比特位 + +==>将定长编码看成变长编码的特例,变长编码不差于定长编码 + + + + + +## 2.哈夫曼编码 + + + +1. 首先,统计得到每一种字符的概率 +2. 将 n 个字符,建立成一棵哈弗曼树 +3. 每一个字符,都落在叶子结点上 +4. 按照左0,右1的形式,将编码读取出来 + + + +![截屏2020-12-29 上午10.14.23](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-12-29%20%E4%B8%8A%E5%8D%8810.14.23.png) + + + +得到新编码: + +a: 0 | b: 110 | 0: 10 | 1: 111 + +平均编码长度:$1*0.8+3*0.05+2*0.1+3*0.05=1.3$ + + + +结论:哈弗曼编码,是最优的变长编码 + + + + + + + +## 4.公式证明 + +1. 首先表示平均编码长度,求解公式最优解 +2. 最终,和熵与交叉熵之间的关系 + + + + 证明$\sum{l_i}\times{p_i}$最小,令$l = -l’$,即证明$-\sum{l_i}\times{p_i}$最小 + +约束$\sum{l_i}\times{p_i} <= 1$ + + + +截屏2020-12-29 下午12.28.35 + + + +截屏2020-12-29 下午12.45.54 + + + +截屏2020-12-29 下午1.02.11 + + + + + +截屏2020-12-30 上午9.35.56 + + + +截屏2020-12-30 上午9.51.45 + + + + + +## 5.代码演示 + + + +```c +#include +#include + +#define swap(a, b) { \ + __typeof(a) __c = a; \ + a = b, b = __c; \ +} + +typedef struct Node { + char ch; + double p;//概率值 + struct Node *lchild, *rchild; +} Node; + +Node *getNewNode(char _ch, double _per) { + Node *p = (Node *)malloc(sizeof(Node)); + p->ch = _ch; + p->p = _per; + p->lchild = p->rchild = NULL; + return p; +} + +Node *CombinNode(Node *a, Node *b) {//合并成新结点 + Node *p = getNewNode(0, a->p + b->p); + p->lchild = a; + p->rchild = b; + return p; +} + +void pick_min(Node **arr, int n) {//找到概率最小的结点 + for (int j = n - 1; j >= 0; --j) { + if (arr[n]->p > arr[j]->p) { + swap(arr[n], arr[j]); + } + } + return ; +} + +Node *getHaffmanTree(Node **arr, int n) {//生成n个结点的哈夫曼树,n个结点循环n-1次形成树 + for (int i = 1; i < n; i++) { + pick_min(arr, n - i);//最小的放在最后面,次之放倒数第二 + pick_min(arr, n - i - 1); + arr[n - i - 1] = CombinNode(arr[n - i], arr[n - i - 1]); + } + return arr[0]; +} + +void __output_encode(Node *root, char *str, int k) { + str[k] = 0; + if (root->lchild == NULL && root->rchild == NULL) {//根节点 + printf("%c %s\n", root->ch, str); + return ; + } + str[k] = '0'; + __output_encode(root->lchild, str, k + 1);//左0右1 + str[k] = '1'; + __output_encode(root->rchild, str, k + 1); + return ; +} + +void output_encode(Node *root) {// + char str[100]; + __output_encode(root, str, 0); + return ; +} + + +void clear(Node *root) { + if (root == NULL) return ; + clear(root->lchild); + clear(root->rchild); + free(root); + return ; +} + +int main() { + int n; + Node **arr;//数组 + scanf("%d", &n);//要输节点数量 + arr = (Node **)malloc(sizeof(Node *) * n); + for (int i = 0; i < n; i++) { + char ch[10]; + double p; + scanf("%s%lf", ch, &p);//输入节点 + arr[i] = getNewNode(ch[0], p); + } + Node *root = getHaffmanTree(arr, n);//获取哈夫曼树 + output_encode(root);//输出哈夫曼树 + clear(root); + free(arr); + + + return 0; +} +``` + + + + + + + + + +# 6.单调栈和单调队列 + +## 6.1课前热身 + +### 6.1HZOJ-261-数据结构 + +![截屏2021-01-14 下午9.40.35](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-01-14%20%E4%B8%8B%E5%8D%889.40.35.png) + +![截屏2021-01-14 下午9.40.47](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-01-14%20%E4%B8%8B%E5%8D%889.40.47.png) + +1. 关键就是新造一个数据结构,结构定义 + 结构操作 + +2. 模拟光标的功能,左移动、右移动、插入、删除,用对顶栈模拟 + +3. 实现对顶栈,用数组模拟、用链表模拟 + +4. 题目中的 BUG:Query K 中,K 可能大于当前位置 + + ------ + + + +```cpp +#include +#include +#include +#include +using namespace std; + +class NewStruct { +public: + NewStruct() { + sum[0] = 0;//sum[0] = 0; + ans[0] = INT64_MIN; + } + void insert(int x) {//插入元素 + s1.push(x);//在s1中插入 + long long val = s1.top() + sum[s1.size() - 1]; + sum[s1.size()] = val;//前i项和 + ans[s1.size()] = max(ans[s1.size() - 1], val);//前i项和的最大值,在前i项和sum和前i-1项和中的最大值做比较 + return ; + } + void del() { + if (s1.empty()) return ; + s1.pop(); + return ; + } + void move_left() { + if (s1.empty()) return ; + s2.push(s1.top()); + del(); + //s1.pop(); + return ; + } + void move_right() { + if (s2.empty()) return ; + insert(s2.top()); + //s1.push(s2.top()); + s2.pop(); + //sum.pop_back(s2.top()); + //ans.push_back(s1.top() + sum[sum.size() - 1]); + } + long long query(int k) { + return ans[k]; + } +private: + stack s1, s2; + long long sum[1005]; + long long ans[1005]; +}; + +int main() { + long long n; + cin >> n;//输入第一行为操作数 𝑁。 + string op; + int val; + NewStruct s; + for (int i = 0; i < n; i++) { + cin >> op; + switch(op[0]) { + case 'I': cin >> val; s.insert(val); break;//在当前位置插入 𝑥 元素; + case 'D': s.del(); break;// 删除当前位置的元素; + case 'L': s.move_left(); break;//将当前位置左移一位,除非它已经是第一个元素; + case 'R': s.move_right(); break;//将当前位置右移一位,除非它已经是最后一个元素 + case 'Q': cin >> val; cout << s.query(val) << endl; break;//𝑘 在当前位置之前,找到一个最大的 𝑆𝑖(1≤𝑖≤𝑘,𝑆𝑖=𝑎1+𝑎2+...+𝑎𝑖) + default : cout << "error" << endl; break; + } + } + + + return 0; +} +``` + + + +### 6.2HZOJ-263-火车进栈 + +------ + +![截屏2021-01-15 上午10.55.49](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-01-15%20%E4%B8%8A%E5%8D%8810.56.08.png) + +![截屏2021-01-15 上午10.56.08](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-01-15%20%E4%B8%8A%E5%8D%8810.56.08.png) + +------ + + + +1. 当前要进栈的最大数字是 x,序列中当前待出栈的数字是 y + +2. $y <= x$,说明 y 一定是栈顶元素 + +3. $y > x$,将 $[x + 1, y]$ 入栈,此时栈顶元素一定是 y + + a[i]=3421 + + 要进栈的1,待出3 + + 3>1,123进栈,stack=123 + + 4进栈:stack=123,待出栈3(y),待进栈4(x),4>3==>3是栈顶,3出栈,4进栈 + + stack=124,a[i]=3421 + + 依次出栈 + +```cpp +#include +#include +using namespace std; + +int a[30], stack[30], top; + +bool is_valid(int *a, int n) { + int j = 0;//要进栈元素 + top = -1; + for (int i = 0; i < n; i++) { + while(j < a[i]) { + stack[++top] = ++j; + } + if (top == -1 || stack[top] - a[i]) return false; + --top; + } + return true; +} + +int main() { + int n, ans = 20;; + cin >> n; + for (int i = 0; i < n; i++) a[i] = i + 1; + do { + if (!is_valid(a, n)) continue; + for (int i = 0; i < n; i++) { + cout << a[i]; + } + cout << endl; + --ans; + } while(ans && next_permutation(a, a + n)); + return 0; +} +``` + +------ + + + +## 6.2.单调队列 + +![截屏2021-01-15 下午2.11.28](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-01-15%20%E4%B8%8B%E5%8D%882.11.28.png) + +![截屏2021-01-15 下午2.14.28](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-01-15%20%E4%B8%8B%E5%8D%882.14.28.png) + +![截屏2021-01-15 下午2.16.34](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-01-15%20%E4%B8%8B%E5%8D%882.16.34.png) + + + + + +1. 本质问题是:固定查询结尾的 RMQ 问题,例如 $RMQ(x, 7)$ +2. 问题性质:维护滑动窗口最值问题 +3. 入队:将队尾违反单调性的元素淘汰出局,再将当前元素入队 +4. 出队:如果队首元素超出了滑动窗口的范围,队首出队 +5. 队首元素:滑动窗口内的最值 +6. 均摊时间复杂度:$O(1)$ + + + +![截屏2021-01-15 下午2.18.02](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-01-15%20%E4%B8%8B%E5%8D%882.18.02.png) + + + + + +注:单调队列维护的区间最小值一定在队首 + + + +入队操作: + +![截屏2021-01-15 下午2.28.58](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-01-15%20%E4%B8%8B%E5%8D%882.28.58.png) + + + + + + + + + +## 6.3单调队列例题HZOJ-271-滑动窗口 + +![截屏2021-01-15 下午2.43.25](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-01-15%20%E4%B8%8B%E5%8D%882.43.25.png) + +![截屏2021-01-15 下午2.43.34](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-01-15%20%E4%B8%8B%E5%8D%882.43.34.png) + + + +单调队列的裸题,主要要学习代码实现 + +**思考:**单调队列中是记录值还是记录下标的问题 + +**结论:**==记录下标,因为有了下标可以索引到值,记录值则反向不可查== + + + + + +## 6.4单调队列及HZOJ271代码实现 + +```cpp +#include +using namespace std; + +#define MAX_N 300000 +int queue[MAX_N + 5];//维护数组的单调队列,存储数组下标 +int head = 0, tail = 0;//头指针,尾指针 +int val[MAX_N + 5];//数组 + +int main() { + int n, k; + cin >> n >> k;//给出一个长度为 𝑁 的数组,一个长为 𝐾 的滑动窗口 + for (int i = 1; i <= n; i++) { + cin >> val[i]; + } + for (int i = 1; i <= n; i++) {//滑动窗口最小值,用单调递增队列 + while ((tail - head) && val[queue[tail - 1]] > val[i]) --tail; + //队列中有元素,并且当前队尾元素的值>要入队的元素,大于要入队的元素全部出队 + //维护单调队列的单调性 + queue[tail++] = i;//存储下标 + if (queue[head] <= i - k) head++; + if (i >= k) { + i > k && cout << " "; + cout << val[queue[head]]; + } + } + cout << endl; + + head = tail = 0;//队列清空 + for (int i = 1; i <= n; i++) {//滑动窗口最大值,用单调递减队列 + while ((tail - head) && val[queue[tail - 1]] < val[i]) --tail; + //队列中有元素,并且当前队尾元素的值 < 要入队的元素,小于要入队的元素全部出队 + queue[tail++] = i;//存储下标 + if (queue[head] <= i - k) head++; + if (i >= k) { + i > k && cout << " "; + cout << val[queue[head]]; + } + } + cout << endl; + return 0; +} + +``` + + + +## 6.5单调栈 + +![截屏2021-01-151 下午4.34.26](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-01-151%20%E4%B8%8B%E5%8D%884.34.26.png) + +![截屏2021-01-15 下午4.34.59](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-01-15%20%E4%B8%8B%E5%8D%884.34.59.png) + +1. 单调栈保留了单调队列的『入队』操作 +2. 单调栈依然是维护了一种单调性 +3. 问题性质:最近(大于/小于)关系 +4. 入栈之前,符合单调性的栈顶元素,就是我们要找的最近(大于/小于)关系 +5. 均摊时间复杂度:$O(1)$ + +![截屏2021-01-15 下午4.30.37](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-01-15%20%E4%B8%8B%E5%8D%884.30.37.png) + +![截屏2021-01-15 下午4.30.50](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-01-15%20%E4%B8%8B%E5%8D%884.30.50.png) + +![截屏2021-01-15 下午4.31.08](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-01-15%20%E4%B8%8B%E5%8D%884.31.08.png) + + + + + + + +## 6.6单调栈例题HZOJ-264-最大矩形面积 + +![截屏2021-01-15 下午4.56.22](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-01-15%20%E4%B8%8B%E5%8D%884.56.33.png) + +![截屏2021-01-15 下午4.56.33](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-01-15%20%E4%B8%8B%E5%8D%884.56.33.png) + + + + + +1. 分析最优解的性质,是解决问题的第一步 +2. 最大矩形的性质:一定是以其所在区域中最矮的木板为高度的 +3. 以每一块木板做为矩形高度,求能得到的最大矩形面积,最后在所有面积中,取一个最大值 +4. 需要求解:每一块木板最近的高度小于当前木板的位置,所以需要用单调栈 + + + + + +![截屏2021-01-15 下午4.57.18](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-01-15%20%E4%B8%8B%E5%8D%884.57.45.png) + +![截屏2021-01-15 下午4.57.45](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-01-15%20%E4%B8%8B%E5%8D%884.57.45.png) + + + + + +```cpp +#include +using namespace std; +#define MAX_N 100000 +long long stack[MAX_N + 5];//单调栈,记录下标 +long long top;//栈顶指针 +long long h[MAX_N + 5];//记录每一块木板的长度 +long long n; + +long long l[MAX_N + 5],r[MAX_N + 5];//l + +void read() { + //cin >> n; + scanf("%lld", &n); + //输入共一行,第一个数表示矩形的个数 𝑁。接下来 𝑁 个数表示矩形的大小。(1≤𝑁≤100000) + for (long long i = 1; i <= n; i++) scanf("%lld", h + i); + //cin >> h[i]; + return ; +} + +long long solve() { + h[0] = h[n + 1] = -1;//最边上的两块木板,即边界木板 + top = -1; + //求最近小于关系,单调递增栈 + //左边木板 + stack[++top] = 0;//0坐标先压栈 + for (long long i = 1; i <= n; i++) { + while (top != -1 && h[stack[top]] >= h[i]) --top;//出栈 + l[i] = stack[top]; + //第i块木板左边离他最近的且高度小于他的木板编号 + stack[++top] = i;//入栈 + + } + //右边木板 + top = -1;//清空栈 + //求最近小于关系,单调递增栈 + stack[++top] = n + 1;//最右边坐标先压栈 + for (long long i = n; i >= 1; i--) { + while (top != -1 && h[stack[top]] >= h[i]) --top;//出栈 + r[i] = stack[top]; + //第i块木板右边离他最近的且高度小于他的木板编号 + stack[++top] = i;//入栈 + } + long long ans = 0; + for (long long i = 1; i <= n; i++) { + ans = max(ans, h[i] * (r[i] - l[i] - 1)); + } + return ans; +} + +int main() { + read(); + cout << solve() << endl; + return 0; +} + +``` + + + + + +# 7.单调队列和单调栈习题 + +## 7.1HAIZEIOJ51矩形 + + ![截屏2021-02-05 上午10.43.49](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-05%20%E4%B8%8A%E5%8D%8810.43.49.png) + +![截屏2021-01-15 下午8.29.51](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-01-15%20%E4%B8%8B%E5%8D%888.29.51.png) + + + + + +![截屏2021-01-17 上午9.46.36](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-01-17%20%E4%B8%8A%E5%8D%889.46.36.png) + +1. 左上角坐标和右下角坐标可以唯一确定一个子矩形 +2. 确定一行,将问题转换成子问题,右下角坐标落在固定的一行上,求每个点能构成的合法子矩形数量 +3. 通过观察,将问题变成两部分子问题 +4. $f(x)$ 代表以 X 做为右下角坐标所能构成的合法子矩形数量 +5. 首先找到左侧离 X 点最近的,小于 X 点的位置 i +6. $f(x) = h_x\times (x-i) + f(i)$ +7. 因为需要求解离 X 最近的小于 X 的位置,所以要用到单调栈 + + + +```cpp +#include +using namespace std; +#define MAX_N 1000 +#define MOD_NUM 100007 +int stack[MAX_N + 5], top; +int c[MAX_N + 5][MAX_N + 5];//c[i][j]从[i,j]向上数,有多少个连续的白色格子,即矩形高 +int f[MAX_N + 5];// +int n, m; + +void read() { + cin >> n >> m;//第一行输入两个数字 𝑛,𝑚(2≤𝑛,𝑚≤1000),代表矩形的长和宽。 + //接下来 𝑛 行,每行 𝑚 个数字,0 代表黑色格子,1 代表白色格子。 + for (int i = 1; i <= n; i++) { + for (int j = 1; j <= m; j++) { + cin >> c[i][j]; + if (c[i][j] == 1) c[i][j] += c[i - 1][j]; + } + } + return ; +} + +int solve() { + long long ans = 0; + for (int i = 1; i <= n; i++) {//n行 + top = -1; + stack[++top] = 0; + c[i][0] = -1; + f[0] = 0; + for (int j = 1; j <= m; j++) {//每行m个数字 + while(top != -1 && c[i][stack[top]] > c[i][j]) --top;//出栈,找到第一个比c[i][j]矮的 + f[j] = c[i][j] * (j - stack[top]) + f[stack[top]];//子矩形个数=矩形高x宽(即f(i)右边的子矩形个数) + f(i)左边的子矩形个数 + //cout << "f[j]" << j << " " << f[j] << endl; + stack[++top] = j; + f[j] %= MOD_NUM; + ans += f[j]; + ans %= MOD_NUM; + } + } + return ans; +} + + +int main() { + read(); + cout << solve() << endl; + + + + return 0; +} +``` + + + + + + + +## 7.2HZOJ-52-古老的打字机 + +![截屏2021-01-17 上午11.00.47](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-01-17%20%E4%B8%8A%E5%8D%8811.00.47.png) + +![截屏2021-01-17 上午11.00.58](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-01-17%20%E4%B8%8A%E5%8D%8811.00.58.png) + + + + + +### 状态定义 + +$dp[i]$ 代表打印前 i 个字符的最小消耗值 + + + +### 状态转移方程 + +定义: $s_i = \sum_{k=1}^{i}c_k$ + +$dp[i] = min(dp[j] + (s_i - s_j)^2 + M)$ + + + +**时间复杂度:**$O(n^2)$ + + + +### 斜率优化 + +假设从 j 转移要优于从 k 转移 + +$dp[j] + (s_i-s_j)^2+M < dp[k] + (s_i-s_k)^2+M$ + +$dp[j] + s_j^2-2s_is_j < dp[k] + s_k^2-2s_is_k$ + +$(dp[j] + s_j^2) -(dp[k] + s_k^2)< 2s_i(s_j-s_k)$ + +$\frac{(dp[j] + s_j^2) -(dp[k] + s_k^2)}{s_j-s_k}< 2s_i$ + +设:$f(i) = dp[i] + s_i^2$ + +$\frac{f(j) - f(k)}{s_j-s_k}< 2s_i$ ,这东西就是一个斜率 + + + +![截屏2021-01-17 上午11.08.30](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-01-17%20%E4%B8%8A%E5%8D%8811.08.58.png) + +![截屏2021-01-17 上午11.09.10](Desktop/%E6%88%AA%E5%B1%8F2021-01-17%20%E4%B8%8A%E5%8D%8811.09.10.png) + +![截屏2021-01-17 上午11.09.25](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-01-17%20%E4%B8%8A%E5%8D%8811.09.25.png) + +==>$\frac{f(j) - f(k)}{s_j-s_k}< 2s_i$ 意味着从j点转移比从k点转移更优秀 + + + +![截屏2021-01-17 上午11.09.34](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-01-17%20%E4%B8%8A%E5%8D%8811.09.34.png) + +1.从k转移优于从j转移,从l转移优于从k转移,所以选择从l转移 + +2.。 + +3.. + +![截屏2021-01-17 上午11.09.39](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-01-17%20%E4%B8%8A%E5%8D%8811.09.39.png) + +经过斜率优化以后,时间复杂度优化成了:$O(n)$ + + + +```cpp +#include +using namespace std; + +#define MAX_N 1000000 +#define S(a) ((a) * (a)) +long long dp[MAX_N + 5], c[MAX_N + 5], s[MAX_N + 5]; +int n, M; +long long f[MAX_N + 5]; +int q[MAX_N + 5], head, tail; + +double slope(int i, int j) {//求斜率 + return 1.0 * (f[i] - f[j])/(s[i] - s[j]); +} + +void read() { + cin >> n >> M; + //第一行输入两个数字,𝑛,𝑀(1≤𝑛≤106,1≤𝑀≤104) 代表文章中字符数量和打字机单次启动的固定磨损值。 + //第二行输入 𝑛 个数字,第 𝑖 个数字代表文章中第 𝑖 个字符的磨损值 𝐶𝑖(1≤𝐶𝑖≤100)。 + for (int i = 1; i <= n; i++) { + cin >> c[i];//cin >> s[i]; + s[i] = s[i - 1] + c[i];//s[i] = s[i - 1] + s[i]; + } + return ; +} + +void set_dp(int i, int j) {//从i点转移到j点 + dp[i] = dp[j] + S(s[i] - s[j]) + M; + f[i] = dp[i] + S(s[i]); + return ; +} + +long long solve() { + head = tail = 0; + q[tail++] = 0; + for (int i = 1; i <= n; i++) { + while (tail - head >= 2 && slope(q[head + 1], q[head]) < 2 * s[i]) ++head;// + set_dp(i, q[head]); + while (tail - head >= 2 && slope(i, q[tail - 1]) < slope(q[tail - 2], q[tail - 1])) --tail; + q[tail++] = i; + } + return dp[n]; +} + +int main() { + read(); + cout << solve() << endl; + return 0; +} +``` + + + +## 三、HZOJ-372-双生序列**1** + +1. 因为两个序列的每个区间的 RMQ 值都相等,等价于两个序列的单调队列长得一样 +2. 将两个序列,依次插入到单调队列中,过程中判断单调队列是否一样,如果不一样,就退出 +3. 所以,需要使用单调队列 + +``` + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +using namespace std; +#define MAX_N 500000 + +class Queue { +public : + Queue(int *arr) : arr(arr) {} + void push(int i) { + while (tail - head && arr[q[tail - 1]] > arr[i]) --tail; + q[tail++] = i; + return ; + } + void pop() { ++head; } + int size() { return tail - head; } + +private: + int *arr; + int q[MAX_N + 5], head, tail; +}; + +int a[MAX_N + 5], b[MAX_N + 5]; +int n; +Queue q1(a), q2(b); + +void read() { + cin >> n; + for (int i = 0; i < n; i++) cin >> a[i]; + for (int i = 0; i < n; i++) cin >> b[i]; + return ; +} + +int main() { + read(); + int p; + for (p = 0; p < n; p++) { + q1.push(p); + q2.push(p); + if (q1.size() != q2.size()) break; + } + cout << p << endl; + return 0; +} + +``` + + + +## 四、HZOJ-270-最大子序和1 + +1. 有个限制条件:子序列的长度不超过 M +2. 转换成前缀和数组上的问题,就是 $S_i - S_j$,其中$i-j <= M$ +3. 在前缀和数组上,维护一个大小为 M 的滑动窗口中的最小值 +4. 所以,采用单调队列维护区间最小值 + +![image-20210110214945169](../guanghu/Library/Application%20Support/typora-user-images/image-20210110214945169.png) + +``` + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +using namespace std; +#define MAX_N 300000 +int s[MAX_N + 5], n, m; +int q[MAX_N + 5], head, tail; + +void read() { + cin >> n >> m; + for (int i = 1; i <= n; i++) { + cin >> s[i]; + s[i] += s[i - 1]; + } + return ; +} + +int solve() { + int ans = INT_MIN; + head = tail = 0; + q[tail++] = 0; + for (int i = 1; i <= n; i++) { + if (i - q[head] > m) head++; + ans = max(ans, s[i] - s[q[head]]); + while (tail - head && s[q[tail - 1]] > s[i]) --tail; + q[tail++] = i; + } + return ans; +} + +int main() { + read(); + cout << solve() << endl; + return 0; +} + +``` + + + +# 8.双数组字典树 + +![截屏2021-01-27 上午9.54.32](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-01-27%20%E4%B8%8A%E5%8D%889.54.32.png) + + + +![截屏2021-01-27 上午10.16.09](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-01-27%20%E4%B8%8A%E5%8D%8810.16.09.png) + + + + + +![截屏2021-01-27 上午10.21.23](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-01-27%20%E4%B8%8A%E5%8D%8810.21.23.png) + + + + + +![截屏2021-01-27 上午10.25.11](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-01-27%20%E4%B8%8A%E5%8D%8810.25.11.png) + + + + + +![截屏2021-01-27 上午10.31.37](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-01-27%20%E4%B8%8A%E5%8D%8810.31.37.png) + + + + + +![截屏2021-01-27 上午10.53.14](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-01-27%20%E4%B8%8A%E5%8D%8810.53.14.png) + + + + + +![截屏2021-01-27 上午11.03.38](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-01-27%20%E4%B8%8A%E5%8D%8811.03.38.png) + +![截屏2021-01-27 上午11.07.01](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-01-27%20%E4%B8%8A%E5%8D%8811.07.01.png) + +![截屏2021-01-27 上午11.07.20](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-01-27%20%E4%B8%8A%E5%8D%8811.07.20.png) + +![截屏2021-01-27 上午11.12.14](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-01-27%20%E4%B8%8A%E5%8D%8811.12.14.png) + + + + + +## 8.1传统字典树的缺点 + +1. 完全二叉树,实际存储结构是连续数组空间,思维逻辑结构是树型的 +2. 完全二叉树,节省了大量的存储边的空间 +3. 优化思想:节省空间的方法叫做 记录式 改 计算式 +4. $n$ 个节点的字典树,有效使用 $n-1$ 条边,浪费了 $(k-1)*n+1$ 条边的存储空间 +5. 参考完全二叉树的优点,提出了双数组字典树 + + + +## 8.2双数组字典树 + +1. 顾名思义,两个数组代表一棵字典树结构 + +2. base 数组信息与子节点编号相关,base + i 就是第 i 个子节点编号,base数组确定父亲的子孩子 + +3. check 数组信息负责做【亲子鉴定】,check 数组中用正负表示是否独立成词 + +4. 不擅长进行动态插入操作,不适合插入操作 + +5. 一次建立,终身使用 + +6. 为了方便,基于普通字典树实现的双数组字典树 + +7. 增加了 fail 数组,可以完成基于双数组字典树的 AC 自动机 + +8. 超小规模实验结果:双数组字典树压缩效率是 25 倍 + +9. 非常方便的输出到文件中,进行机器之间的共享 + +10. 课后作业:利用真实数据集,测试双数组字典树的压缩效率 + + + +```cpp +#include +#include +using namespace std; + +#define BASE 26 + +typedef struct Node {//字典树 + int flag; + struct Node *next[BASE];//存储26个英文字符的字典树 +} Node; + +typedef struct DANode {//双数组字典树结构 + int base, check;// +} DANode;// + +Node *getNewNode() { + Node *p = (Node *)malloc(sizeof(Node)); + p->flag = 0; + memset(p->next, 0, sizeof(p->next)); + return p; +} + +inline int code(char c) { return c - 'a'; } + +int insert(Node *root, const char *str) {//在字典树中插入 + int cnt = 0; + Node *p = root;//指向当前插入字符串的位置 + for (int i = 0; str[i]; i++) { + int ind = code(str[i]); + if (p->next[ind] == NULL) {//相关子节点为空 + cnt++; + p->next[ind] = getNewNode(); + } + p = p->next[ind]; + } + p->flag = 1; + return cnt;//返回本次插入str时一共生成几个新节点 +} + +int get_base_value(Node *root, DANode *tree, int ind) { + int base = 1, flag; + do { + base += 1; + flag = 1; + for (int i = 0; i < BASE && flag; i++) { + if (root->next[i] == NULL) continue; + if (tree[base + i].check) flag = 0;//如果存在check值,说明base值已经被占用 + } + } while (flag == 0); + return base; +} + +int transform_double_array_trie(Node *root, DANode *tree, int ind) { + //字典树转换成双数组字典树,双数组字典树存储在tree中,ind:根节点在双数组字典树中的下标 + //返回最大的下标值 + if (root == NULL) return 0; + if (root->flag) tree[ind].check = -tree[ind].check;//当前节点独立成词 + int base = get_base_value(root, tree, ind);//获得当前节点的base值 + tree[ind].base = base; + for (int i = 0; i < BASE; i++) { + if (root->next[i] == NULL) continue; + tree[base + i].check = ind;//子节点check存储父节点下标 + } + int max_ind = ind; + for (int i = 0; i < BASE; i++) {//依次确认每一个子节点是base值 + int temp = transform_double_array_trie(root->next[i], tree, base + i); + if (temp > max_ind) max_ind = temp; + } + return max_ind;//返回最大下标 +} + +void clear_trie(Node *root) { + if (root == NULL) return ; + for (int i = 0; i < BASE; i++) { + clear_trie(root->next[i]); + } + free(root); + return ; +} + +int main() { + int n, cnt = 1;//字典树中一共有cnt个节点 + char str[1000]; + scanf("%d", &n);//读入n个单词 + Node *root = getNewNode(); + for (int i = 0; i < n; i++) { + scanf("%s", str);//每次读入一个单词插入字典树中 + cnt += insert(root, str); + } + //将字典树转换成双数组字典树 + size_t tree_size = sizeof(DANode) * (cnt * BASE); + DANode *tree = (DANode *)malloc(tree_size); + memset(tree, 0, tree_size); + int max_ind = transform_double_array_trie(root, tree, 1); + size_t s1 = cnt * sizeof(Node); + size_t s2 = max_ind * sizeof(DANode); + printf("trie(%lu Byte), double array trie(%lu, Byte)\n", s1, s2); + for (int i = 0; i <= max_ind; i++) { + printf("(%d | %d, %d)\t", i, tree[i].base, tree[i].check); + if ((i + 1 ) % 5 == 0) printf("\n"); + } + cout << endl; + free(tree); + clear_trie(root); + + return 0; +} + +``` + + + +```shell +./a.out < 18.input17 +(0 | 0, 0) (1 | 2, 0) (2 | 2, 9) (3 | 5, 19) (4 | 6, 12) +(5 | 17, 7) (6 | 3, 28) (7 | 4, 16) (8 | 2, -22) (9 | 2, 1) +(10 | 3, 2) (11 | 2, -6) (12 | 4, 1) (13 | 2, 3) (14 | 6, 4) +(15 | 2, -13) (16 | 3, 14) (17 | 2, -5) (18 | 20, 27) (19 | 2, 2) +(20 | 4, 18) (21 | 13, 20) (22 | 2, 9) (23 | 2, -21) (24 | 0, 0) +(25 | 0, 0) (26 | 0, 0) (27 | 3, 1) (28 | 2, 10) +``` + +```c +//18.input17 +5 +haizei +kaikeba +harbin +hug +zpark +``` + + + +```cpp +#include +#include +using namespace std; + +#define BASE 26 +#define MSG_LEVEL 1 +#define DEBUG_LEVEL 1 +#define INFO_LEVEL 2 + +#define LOG(level, frm, args...) {\ + if (level >= MSG_LEVEL) {\ + printf(frm, ##args);\ + }\ +} + +#define LOG_DEBUG(args...) LOG(DEBUG_LEVEL, args); +#define LOG_INFO(args...) LOG(INFO_LEVEL, args); + + + +typedef struct Node {//字典树 + int flag; + struct Node *next[BASE];//存储26个英文字符的字典树 +} Node; + +typedef struct DANode {//双数组字典树结构 + int base, check, fail;// +} DANode;// + +Node *getNewNode() { + Node *p = (Node *)malloc(sizeof(Node)); + p->flag = 0; + memset(p->next, 0, sizeof(p->next)); + return p; +} + +inline int code(char c) { return c - 'a'; } + +int insert(Node *root, const char *str) {//在字典树中插入 + int cnt = 0; + Node *p = root;//指向当前插入字符串的位置 + for (int i = 0; str[i]; i++) { + int ind = code(str[i]); + if (p->next[ind] == NULL) {//相关子节点为空 + cnt++; + p->next[ind] = getNewNode(); + } + p = p->next[ind]; + } + p->flag = 1; + return cnt;//返回本次插入str时一共生成几个新节点 +} + +int get_base_value(Node *root, DANode *tree, int ind) { + int base = 1, flag; + do { + base += 1; + flag = 1; + for (int i = 0; i < BASE && flag; i++) { + if (root->next[i] == NULL) continue; + if (tree[base + i].check) flag = 0;//如果存在check值,说明base值已经被占用 + } + } while (flag == 0); + return base; +} + +int transform_double_array_trie(Node *root, DANode *tree, int ind) { + //字典树转换成双数组字典树,双数组字典树存储在tree中,ind:根节点在双数组字典树中的下标 + //返回最大的下标值 + if (root == NULL) return 0; + if (root->flag) tree[ind].check = -tree[ind].check;//当前节点独立成词 + int base = get_base_value(root, tree, ind);//获得当前节点的base值 + tree[ind].base = base; + for (int i = 0; i < BASE; i++) { + if (root->next[i] == NULL) continue; + tree[base + i].check = ind;//子节点check存储父节点下标 + } + int max_ind = ind; + for (int i = 0; i < BASE; i++) {//依次确认每一个子节点是base值 + int temp = transform_double_array_trie(root->next[i], tree, base + i); + if (temp > max_ind) max_ind = temp; + } + return max_ind;//返回最大下标 +} + +void dump_double_array_tree(DANode *tree, int n) { + LOG_INFO("%d\n", n); + for (int i = 0; i <= n; i++) { + LOG_INFO("%d %d \n", tree[i].base, tree[i].check); + } + return ; +} + +void clear_trie(Node *root) { + if (root == NULL) return ; + for (int i = 0; i < BASE; i++) { + clear_trie(root->next[i]); + } + free(root); + return ; +} + +int main() { + int n, cnt = 1;//字典树中一共有cnt个节点 + char str[1000]; + scanf("%d", &n);//读入n个单词 + Node *root = getNewNode(); + for (int i = 0; i < n; i++) { + scanf("%s", str);//每次读入一个单词插入字典树中 + cnt += insert(root, str); + } + //将字典树转换成双数组字典树 + size_t tree_size = sizeof(DANode) * (cnt * BASE); + DANode *tree = (DANode *)malloc(tree_size); + memset(tree, 0, tree_size); + int max_ind = transform_double_array_trie(root, tree, 1); + size_t s1 = cnt * sizeof(Node); + size_t s2 = max_ind * sizeof(DANode); + LOG_DEBUG("trie(%lu Byte), double array trie(%lu, Byte)\n", s1, s2); + LOG_DEBUG("rate : %.2lf\n", 1.0 * s2 / s1); + for (int i = 0; i <= max_ind; i++) { + LOG_DEBUG("(%d | %d, %d)\t", i, tree[i].base, tree[i].check); + if ((i + 1 ) % 5 == 0) LOG_DEBUG("\n"); + } + LOG_DEBUG("\n"); + //将字典树双数组输入到文件中去 + dump_double_array_tree(tree, max_ind); + free(tree); + clear_trie(root); + + return 0; +} +``` + + + +``` +trie(5400 Byte), double array trie(336, Byte) +rate : 0.06 +(0 | 0, 0) (1 | 2, 0) (2 | 2, 9) (3 | 5, 19) (4 | 6, 12) +(5 | 17, 7) (6 | 3, 28) (7 | 4, 16) (8 | 2, -22) (9 | 2, 1) +(10 | 3, 2) (11 | 2, -6) (12 | 4, 1) (13 | 2, 3) (14 | 6, 4) +(15 | 2, -13) (16 | 3, 14) (17 | 2, -5) (18 | 20, 27) (19 | 2, 2) +(20 | 4, 18) (21 | 13, 20) (22 | 2, 9) (23 | 2, -21) (24 | 0, 0) +(25 | 0, 0) (26 | 0, 0) (27 | 3, 1) (28 | 2, 10) +28 +0 0 +2 0 +2 9 +5 19 +6 12 +17 7 +3 28 +4 16 +2 -22 +2 1 +3 2 +2 -6 +4 1 +2 3 +6 4 +2 -13 +3 14 +2 -5 +20 27 +2 2 +4 18 +13 20 +2 9 +2 -21 +0 0 +0 0 +0 0 +3 1 +2 10 +``` + + + +## 8.3在双数组字典树中建立AC自动机 + +```cpp +#include +#include +using namespace std; + +#define BASE 26 +#define MSG_LEVEL 1 +#define DEBUG_LEVEL 1 +#define INFO_LEVEL 2 + +#define LOG(level, frm, args...) {\ + if (level >= MSG_LEVEL) {\ + printf(frm, ##args);\ + }\ +} + +#define LOG_DEBUG(args...) LOG(DEBUG_LEVEL, args); +#define LOG_INFO(args...) LOG(INFO_LEVEL, args); + + + +typedef struct Node {//字典树 + int flag; + struct Node *next[BASE];//存储26个英文字符的字典树 +} Node; + +typedef struct DANode {//双数组字典树结构 + int base, check, fail;//fail用于在双数组字典树中建立AC自动机 +} DANode;// + +Node *getNewNode() { + Node *p = (Node *)malloc(sizeof(Node)); + p->flag = 0; + memset(p->next, 0, sizeof(p->next)); + return p; +} + +inline int code(char c) { return c - 'a'; } + +int insert(Node *root, const char *str) {//在字典树中插入 + int cnt = 0; + Node *p = root;//指向当前插入字符串的位置 + for (int i = 0; str[i]; i++) { + int ind = code(str[i]); + if (p->next[ind] == NULL) {//相关子节点为空 + cnt++; + p->next[ind] = getNewNode(); + } + p = p->next[ind]; + } + p->flag = 1; + return cnt;//返回本次插入str时一共生成几个新节点 +} + +int get_base_value(Node *root, DANode *tree, int ind) { + int base = 1, flag; + do { + base += 1; + flag = 1; + for (int i = 0; i < BASE && flag; i++) { + if (root->next[i] == NULL) continue; + if (tree[base + i].check) flag = 0;//如果存在check值,说明base值已经被占用 + } + } while (flag == 0); + return base; +} + +int transform_double_array_trie(Node *root, DANode *tree, int ind) { + //字典树转换成双数组字典树,双数组字典树存储在tree中,ind:根节点在双数组字典树中的下标 + //返回最大的下标值 + if (root == NULL) return 0; + if (root->flag) tree[ind].check = -tree[ind].check;//当前节点独立成词 + int base = get_base_value(root, tree, ind);//获得当前节点的base值 + tree[ind].base = base; + for (int i = 0; i < BASE; i++) { + if (root->next[i] == NULL) continue; + tree[base + i].check = ind;//子节点check存储父节点下标 + } + int max_ind = ind; + for (int i = 0; i < BASE; i++) {//依次确认每一个子节点是base值 + int temp = transform_double_array_trie(root->next[i], tree, base + i); + if (temp > max_ind) max_ind = temp; + } + return max_ind;//返回最大下标 +} + +void dump_double_array_tree(DANode *tree, int n) { + LOG_INFO("%d\n", n); + for (int i = 0; i <= n; i++) { + LOG_INFO("%d %d %d\n", tree[i].base, tree[i].check, tree[i]. fail); + } + return ; +} + +void clear_trie(Node *root) { + if (root == NULL) return ; + for (int i = 0; i < BASE; i++) { + clear_trie(root->next[i]); + } + free(root); + return ; +} + +void build_ac_base_double_array_trie(DANode *tree, int max_ind) { + int *que = (int *)malloc(sizeof(int) * (max_ind + 5)); + int head, tail; + head = tail = 0; + tree[1].fail = 0; + for (int i = 0; i < BASE; i++) { + int child_ind = tree[1].base + i; + if (abs(tree[child_ind].check) != 1) continue;//第i个节点有子孩子 + tree[child_ind].fail = 1;//fail节点指向子孩子 + que[tail++] = child_ind; + } + while (head < tail) { + int p = que[head++];//当前节点不为空,取出当前节点 + for (int i = 0; i < BASE; i++) { + int c = tree[p].base + i, k = tree[p].fail; + if (abs(tree[c].check) != p) continue; + while (k && tree[tree[k].base + i].check != k) k = tree[k].fail;//k节点下面没有第k个子孩子 + if (k == 0) k = 1; + if (abs(tree[tree[k].base + i].check) == k) k = tree[k].base + i; + tree[c].fail = k;//初始化c节点的fail值 + que[tail++] = c; + } + } + free(que); + return ; +} + + +int main() { + int n, cnt = 1;//字典树中一共有cnt个节点 + char str[1000]; + scanf("%d", &n);//读入n个单词 + Node *root = getNewNode(); + for (int i = 0; i < n; i++) { + scanf("%s", str);//每次读入一个单词插入字典树中 + cnt += insert(root, str); + } + //将字典树转换成双数组字典树 + size_t tree_size = sizeof(DANode) * (cnt * BASE); + DANode *tree = (DANode *)malloc(tree_size); + memset(tree, 0, tree_size); + int max_ind = transform_double_array_trie(root, tree, 1); + size_t s1 = cnt * sizeof(Node); + size_t s2 = max_ind * sizeof(DANode); + LOG_DEBUG("trie(%lu Byte), double array trie(%lu, Byte)\n", s1, s2); + LOG_DEBUG("rate : %.2lf\n", 1.0 * s2 / s1); + for (int i = 0; i <= max_ind; i++) { + LOG_DEBUG("(%d | %d, %d)\t", i, tree[i].base, tree[i].check); + if ((i + 1 ) % 5 == 0) LOG_DEBUG("\n"); + } + LOG_DEBUG("\n"); + //将字典树双数组输入到文件中去 + build_ac_base_double_array_trie(tree, max_ind); + dump_double_array_tree(tree, max_ind); + free(tree); + clear_trie(root); + + return 0; +} + +``` + + + +``` +trie(5400 Byte), double array trie(336, Byte) +rate : 0.06 +(0 | 0, 0) (1 | 2, 0) (2 | 2, 9) (3 | 5, 19) (4 | 6, 12) +(5 | 17, 7) (6 | 3, 28) (7 | 4, 16) (8 | 2, -22) (9 | 2, 1) +(10 | 3, 2) (11 | 2, -6) (12 | 4, 1) (13 | 2, 3) (14 | 6, 4) +(15 | 2, -13) (16 | 3, 14) (17 | 2, -5) (18 | 20, 27) (19 | 2, 2) +(20 | 4, 18) (21 | 13, 20) (22 | 2, 9) (23 | 2, -21) (24 | 0, 0) +(25 | 0, 0) (26 | 0, 0) (27 | 3, 1) (28 | 2, 10) +28 +0 0 0 +2 0 0 +2 9 1 +5 19 1 +6 12 1 +17 7 1 +3 28 1 +4 16 1 +2 -22 1 +2 1 1 +3 2 1 +2 -6 1 +4 1 1 +2 3 1 +6 4 1 +2 -13 1 +3 14 12 +2 -5 1 +20 27 1 +2 2 1 +4 18 1 +13 20 1 +2 9 1 +2 -21 12 +0 0 0 +0 0 0 +0 0 0 +3 1 1 +2 10 27 +``` + + + + + +## 8.4 二叉字典树 + +1. 计算机中所有信息都是二进制存储的 +2. 任何信息都可以看成一个二进制串 +3. 插入二进制串的字典树,就是二叉字典树 +4. 二叉字典树可以存储任意信息 +5. 节省空间,浪费时间,本质:时间换空间的算法思维 +6. 哈弗曼编码 + 二叉字典树 可能更配哦,既节省了空间,又在最大限度上节省了查找时间 + + + + + + + +**建议:**多看几本基本的算法书、【数论基础】、多接触离散型数学思维 + + + +end 2021.1.30 + + + +不积跬步,无以至千里。不积小流,无以成江海。 + + + + diff --git "a/02.c++\347\254\224\350\256\260/05.\351\235\242\350\257\225\347\254\224\350\257\225\347\256\227\346\263\225\344\270\213.md" "b/02.c++\347\254\224\350\256\260/05.\351\235\242\350\257\225\347\254\224\350\257\225\347\256\227\346\263\225\344\270\213.md" new file mode 100644 index 0000000..1f23ea9 --- /dev/null +++ "b/02.c++\347\254\224\350\256\260/05.\351\235\242\350\257\225\347\254\224\350\257\225\347\256\227\346\263\225\344\270\213.md" @@ -0,0 +1,3829 @@ +\--- + +title: 面试笔试算法下 + +date: 2020-12-22 08:08:15 + +tags: 面试笔试算法下 + +categories: 面试笔试算法下 + +\--- + + + + + +[TOC] + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +# 1.线段树 + + + +## 0.线段树结构 + + + +截屏2020-12-31 上午11.32.22 + + + +截屏2020-12-31 上午11.32.35 + + + + + +## 1.问题背景 + +1. 单点修改,区间查询(基础版) +2. 区间修改,区间查询(进阶版) +3. 单点修改,单点查询(用不着线段树) +4. 区间修改,单点查询(是第二种情况的特例) + + + + + +## 2.线段树区间查询及单点修改 + + + +### 1.操作 + +Modify(7,9) :修改7位置上的值为9 + +Query(2, 6) : 查询[2,6]区间的和值==> 30 + +Modify(2,3) :修改2位置上的值为3 + +Query(2, 6) : 查询[2,6]区间的和值==> 25 + + + +截屏2020-12-31 上午11.32.10 + + + +### 2.具体过程 + +1)Modify(2,3) + + + +截屏2020-12-31 上午11.32.42 + + + +2)将2上面的值改为3 + +截屏2020-12-31 上午11.32.48 + +3)更新根节点上的和值 + +截屏2020-12-31 上午11.32.56 + +4)Query(2, 6) : + +截屏2020-12-31 上午11.33.02 + + + + + +## 3.基础版线段树 + +1. 线段树是对于一维序列的一种维护结构 +2. 采用的分治的思想,将总区间分成左右两部分,一直进行下去,直到区间中只剩下一个节点为止 +3. 线段树的叶子结点,代表了原序列中的单个位置的值 +4. 如果采用完全二叉树的存储结构的话,最起码需要 $4n$ 的存储空间 +5. 当面对区间修改的时候,基础版的线段树效率上还不如直接在一维序列上修改 +6. 只适用于单点修改,区间查询 + + + + + + + +## 4.代码实现 + +![截屏2020-12-31 上午11.37.41](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-12-31%20%E4%B8%8A%E5%8D%8811.37.41.png) + + + + + + + +```cpp +#include +#include +using namespace std; + +//海贼OJ222 + +#define MAX_N 10000 + +struct { + int max_num;//结点和值 + int l,r; +} tree[MAX_N << 2]; +int arr[MAX_N + 5]; + +void update(int ind) {//更新结点的和值 + tree[ind].max_num = max(tree[ind << 1].max_num, tree[ind << 1 | 1].max_num); + return ; +} + +void build_tree(int ind, int l, int r) {//创建线段树,在tree[ind]上插入l,r + tree[ind].l = l; + tree[ind].r = r; + if (l == r) {//l==r,l、r各代表一个值,不能再分裂 + tree[ind].max_num = arr[l];//将值挂在叶子结点上 + return ; + } + int mid = (l + r) >> 1; + build_tree(ind * 2, l, mid);//递归左子树 + build_tree(ind * 2 + 1, mid + 1, r);//递归右子树 + update(ind);//更新和值 + return ; +} + +void modify(int ind, int k, int val) {//修改tree[ind]上tree[k]的值为value,ind代表当前节点的编号 + if (tree[ind].l == tree[ind].r) { + tree[ind].max_num = val; + return ; + } + int mid = (tree[ind].l + tree[ind].r) >> 1; + if (k <= mid ) { + modify(ind << 1, k, val); + } else { + modify(ind << 1 | 1, k, val); + } + update(ind); + return ; +} + +int Query(int ind, int x, int y) {//查询[x,y]的和值 + if (tree[ind].l >= x && tree[ind].r <= y) {//要查找的值在这个根的 + return tree[ind].max_num; + } + int ans = INT_MIN; + int mid = (tree[ind].l + tree[ind].r) >> 1; + if (mid >= x) {//在左子树中查找 + ans = max(ans, Query(ind << 1, x, y)); + } + if (mid < y) {//在右子树中查找 + ans = max(ans, Query(ind << 1 | 1, x, y)); + } + return ans; +} + + + +int main() { + + int m, n; + scanf("%d%d", &n, &m); + for (int i = 1; i <= n; i++) { + scanf("%d", arr + i); + } + build_tree(1, 1, n); + int a, b, c; + for (int i = 0; i < m; i++) { + scanf("%d%d%d", &a, &b, &c); + switch (a) { + case 1: modify(1, b, c); break; + case 2: { + if (b > c) { + cout << "-2147483648" << endl; + break; + } + cout << Query(1, b, c) << endl; + } break; + } + } + + + + return 0; +} +``` + + + + + +==>总结 + +单点查询:log(n) + +区间查询:log(n) + +> 1.若采用完全能二叉树的存储方式,n个节点的线段树,最多需要多少个节点空间? +> +> $n + n - 1 + 2n = 4n -1$ +> +> 2.如何做区间修改 + + + +![截屏2020-12-31 下午10.56.59](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-12-31%20%E4%B8%8B%E5%8D%8810.56.59.png) + + + + + +![截屏2020-12-31 下午10.59.54](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-12-31%20%E4%B8%8B%E5%8D%8810.59.54.png) + + + +其实节点上l,r可以省略 + + + +![截屏2020-12-31 下午11.01.08](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-12-31%20%E4%B8%8B%E5%8D%8811.01.08.png) + + + + + + + + + +```cpp +#include +#include +using namespace std; + +//海贼OJ222 + +#define MAX_N 10000 + +struct { + int max_num;//子树的和值 +} tree[MAX_N << 2]; +int arr[MAX_N + 5]; + +void update(int ind) {//更新结点的和值 + tree[ind].max_num = max(tree[ind << 1].max_num, tree[ind << 1 | 1].max_num); + return ; +} + +void build_tree(int ind, int l, int r) {//创建线段树,在ind上插入l,r + if (l == r) {//l==r,l、r各代表一个值,不能再分裂 + tree[ind].max_num = arr[l]; + return ; + } + int mid = (l + r) >> 1; + build_tree(ind * 2, l, mid); + build_tree(ind * 2 + 1, mid + 1, r); + update(ind); + return ; +} + +void modify(int ind, int l, int r, int k, int val) {//在tree[ind](ind当前节点位置)维护的区间[l, r],上将tree[k]的值修改为val、 + if (l == r) { + tree[ind].max_num = val; + return ; + } + int mid = (l + r) >> 1; + if (k <= mid ) { + modify(ind << 1, l, mid, k, val); + } else { + modify(ind << 1 | 1, mid + 1, r, k, val); + } + update(ind); + return ; +} + +int Query(int ind, int l, int r, int x, int y) {//查询 + if (l >= x && r <= y) {//要查找的值在这个根的 + return tree[ind].max_num; + } + int ans = INT_MIN; + int mid = (l + r) >> 1; + if (mid >= x) {//在左子树中查找 + ans = max(ans, Query(ind << 1, l, mid, x, y)); + } + if (mid < y) {//在右子树中查找 + ans = max(ans, Query(ind << 1 | 1, mid + 1, r, x, y)); + } + return ans; +} + + + +int main() { + + int m, n; + scanf("%d%d", &n, &m);//n,数组长度,m,操作数 + for (int i = 1; i <= n; i++) { + scanf("%d", arr + i); + } + build_tree(1, 1, n);//初始化线段树tree,根节点的范围是[1, n] + int a, b, c; + for (int i = 0; i < m; i++) { + scanf("%d%d%d", &a, &b, &c); + switch (a) { + case 1: modify(1, 1, n, b, c); break;//在根节点[1, n]上修改tree[b]的值为c + case 2: { + if (b > c) { + cout << "-2147483648" << endl; + break; + } + cout << Query(1, 1, n, b, c) << endl;//在根节点tree[1,n]上查找[b, c]区间上的和值 + } break; + } + } + + + + return 0; +} +``` + + + + + + + +## 5.线段树区间修改 + +明朝时期有一位官员,皇帝下放粮食,下发到县令手里,县令自己拿着粮食,不下发,突然有一天,皇帝要微服私访,此时县令下发粮食 + + + +### 1.Modify(0, 6, +3) + +1)在区间[0, 6]的结点上加3,此时在懒标记上加3而不是在叶子结点上加3,县令自己拿了粮食不下发给百姓 + +截屏2020-12-31 下午11.02.12 + + + +2)叶子结点的值不变,但是区间根节点的值发生改变,农民没有拿到粮食,但是县令拿到了,县令上报农民已经拿到了粮食,向上更新区间和值 + +截屏2020-12-31 下午11.05.35 + +### 2.Query(2,7) + +1)查询[2,7]区间上的和值 + +截屏2020-12-31 下午11.07.33 + + + +2)开始递归向下查找 + +截屏2020-12-31 下午11.08.27 + + + + + +3) `2 < 4` = ( 9 / 2) 到左子树查找 + +截屏2020-12-31 下午11.09.27 + + + +4)左子树中有懒标记,皇帝要来视察民情,县令下方粮食,懒标记下沉, + +截屏2020-12-31 下午11.09.58 + + + +5) 继续向下查询, + + `2 = 2` = ( 4 / 2 ) 向左子树递归 + +截屏2020-12-31 下午11.10.12 + + + +6)左子树有懒标记,懒标记下沉 + +截屏2020-12-31 下午11.10.24 + + + +7) `2 > 1` = 2 / 2 ,在右子树中查询, + +截屏2020-12-31 下午11.10.38 + + + +8)找到节点2,更新结点的值,返回节点2的值 + +截屏2020-12-31 下午11.10.57 + + + +9) 向上回溯,(4/2 + 1) = `3 > 2`,4 < 7 递归到右子树 + +截屏2020-12-31 下午11.11.10 + + + +10) 结点的区间在待查找区间范围内,直接返回3-4结点的值 + +截屏2020-12-31 下午11.11.22 + + + +11) 向上回溯,(0 + 9) / 2 + 1= 5 < 7 递归到右结点5–-9 + +截屏2020-12-31 下午11.11.39 + + + + + +12) (5 + 9) / 2 = 7 == 7,递归5–9左子树 + +截屏2020-12-31 下午11.11.51 + +13) 找到结点 ,返回值 + +截屏2020-12-31 下午11.12.06 + + + +14) 向上回溯,返回区间和值 + +截屏2020-12-31 下午11.12.16 + + + + + + + +## 6.进阶版线段树 + +1. 优化掉了代码实现中的:l,r。区间变量 +2. 可以用于区间更新,区间查询 +3. 增加了懒标记,达到能够区间更新的目的 +4. 懒标记是需要向下下沉的 +5. ==标记下沉发生在递归之前,向上更新发生在具有修改操作的递归之后== + + + + + +## 7.代码实现 + + + +![截屏2020-12-31 下午10.48.39](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-12-31%20%E4%B8%8B%E5%8D%8810.48.39.png) + + + +```cpp +#include +using namespace std; + +#define MAX_N 10000 +typedef long long intl; +intl flag = 0; +struct { + intl sum;//和值 + intl tag;//懒标记 +} tree[MAX_N << 2]; + +intl arr[MAX_N + 5]; + +void update(intl ind) {//更新tee[ind]结点和值 + tree[ind].sum = tree[ind << 1].sum + tree[ind << 1 | 1].sum; + //tree[ind].sum = tree[ind * 2].sum + tree[ind * 2 + 1].sum; + return ; +} + +void down(intl index, intl left, intl right) {//懒标记下沉 + if (tree[index].tag) {//如果存在懒标记值 > 0 + intl mid = (left + right) >> 1; + intl value = tree[index].tag; + tree[index << 1].sum += value * (mid - left + 1);//懒标记下沉左子树 + tree[index << 1].tag += value; + tree[index << 1 | 1].sum += value * (right - mid);//right - (mid + 1)+1//懒标记下沉右子树 + tree[index << 1 | 1].tag += value; + tree[index].tag = 0; + } + return ; +} + +void build_tree(intl index, intl left, intl right) { + //初始化线段树tree[index],线段树区间为[left, right],right = n,即有n个值要存在线段树中 + if (left == right) {//左右区间相等,即为叶子节点,存放值 + tree[index].sum = arr[left]; + return ; + } + intl mid = (left + right) >> 1; + build_tree(index << 1, left, mid);// + build_tree(index << 1 | 1, mid + 1, right); + update(index); + return ; +} +void modify(intl index, intl left, intl right, intl x, intl y, intl value) { + flag && printf("modify(%lld, %lld, %lld) : %lld, %lld, %lld, %lld\n", + x, y, value, index, left, right, tree[index].sum + ); + //从根节点tree[index]开始查询,根节点的区间是[left, right],找到区间[x, y],把[x, y]区间上的值加上value + if (x <= left && right <= y) {//待操作区间在包含区间的范围内,即要修改的区间比根节点区间大 + tree[index].sum += value * (right - left + 1); + tree[index].tag += value;//懒标记,皇帝给农民发放粮食,县令不放 + return ; + } + down(index, left, right);//懒标记下沉,皇帝要微服私访,县令下发粮食 + intl mid = (left + right) >> 1; + if (mid >= x) { + modify(index << 1, left, mid, x, y, value); + } + if (mid < y) { + modify (index << 1 | 1, mid + 1, right, x, y, value); + } + update(index); + return ; +} + +intl Query(intl index, intl left, intl right, intl x, intl y) { + if (x <= left && right <= y) {//要查询的区间[x, y]在[left, right]范围内 + return tree[index].sum; + } + down(index, left, right); + intl mid = (left + right) >> 1; + intl ans = 0; + if (mid >= x) { + ans += Query(index << 1, left, mid, x, y); + } + if (mid < y) { + ans += Query(index << 1 | 1, mid + 1, right, x, y); + } + return ans; +} + + + + +int main() { + intl n, m; + scanf("%lld%lld", &n, &m); + for (intl i = 1; i <= n; i++) { + scanf("%lld", arr + i); + } + build_tree(1, 1, n); + intl a, b, c, d; + for (intl i = 0; i < m; i++) { + scanf("%lld%lld%lld", &a, &b, &c); + switch (a) { + case 1: { + scanf("%lld", &d); + modify(1, 1, n, b, c, d); + }break; + case 2: { + if (b > c) { + printf( "0\n" ); + break; + } + printf("%lld\n", Query(1, 1, n, b, c));//从根节点开始查询,根节点的范围是[1,n],查询[b,c]区间范围内的值 + }break; + } + } + + + return 0; +} +``` + + + + + +# 2.从递推动归(上) + +## 1.兔子繁殖问题 + +兔子繁殖问题:斐波那切数列 + +**题目:**如果1对兔子每月能生1对小兔子,而每对小兔在它出生后的第3个月就可以生1对小兔子,如果从1对初生的小兔子开始,1年后能繁殖多少兔子? + + + + + +截屏2021-01-01 下午8.10.32 + + + + + + + + + + + +![截屏2021-01-01 下午8.12.49](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-01-01%20%E4%B8%8B%E5%8D%888.12.49.png) + + + +> 思考:当存在n = 40,程序会有什么问题?当n=60呢? + + + +1. 程序运行效率问题(会出现重复计算的现象,如下图) + + 解决方法:递归过程加记忆化(用数组记忆) 或 改成逆向递推求解(从f(1)求解到f(n),利用循环求解) + +2. 程序计算结果超出整数范围,结果溢出出错 + + 解决方法:改成大整数求解 + + + +截屏2021-01-01 下午8.15.11 + + + + + +## 2.如何求解递推问题 + + + +截屏2021-01-01 下午8.27.42 + + + +> 递推问题求解套路 + +1. 确定递推状态,一个数学符号 + 一个数学符号的语义解释 +2. 确定递推公式,推导递推状态符号的自我表示方法 +3. 程序实现,(递归+记忆化 / 循环实现) + + + + + +### 确定递推状态 + +注意:这是学习递推问题的重中之重。学习确定递推状态的技巧。 + +$f(x) = y$ + +y:问题中的求解量,也是我们所谓的因变量 + +x:问题中直接影响求解量的部分,也是我们所谓的自变量 + +本质:就是寻找问题中的自变量与因变量 + + + +### 推导递推公式 + +本质:分析状态中的容斥关系(互相排斥) + +$f(n)=f(n-1)+f(n-2)$ + +$f(n-1)$,代表 n-1个月的兔子数量,恰巧等于第 n 个月的成年兔子数量 + +$f(n-2)$,代表 n-2个月的兔子数量,恰巧等于第 n 个月的幼年兔子数量 + +所谓的推导,就是推导上面的这两句话的内容 + + + + + +## 3.爬楼梯问题 + + + +lALPDiQ3NHubpPPNB3LNCzg_2872_1906.png_720x720g + + + +### 状态定义 + +$f(n)$ 代表爬 n 节楼梯的方法总数 + +### 递推公式 + +$f(n)=f(n - 2) + f(n - 3)$ + + + +==>用容斥原理对最后一种台阶的情况进行分类,展开讨论,分类后求和(站在最后面往前看) + + + +截屏2021-01-01 下午11.03.04 + +截屏2021-01-01 下午11.00.14 + + + + + +## 4.凑钱币问题 + + + +![lALPDgfLP1Vs-lrNCBrNC5Q_2964_2074.png_720x720g](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/lALPDgfLP1Vs-lrNCBrNC5Q_2964_2074.png_720x720g.jpg) + + + +### 状态定义 + +$f(n, m)$ 代表用前 n 种钱币,拼凑 m 元钱的方案总数 + +### 递推公式 + +$f(n, m) = f(n - 1, m) + f(n, m - val[n])*1$ + +没有使用n种钱币的方法+使用n种钱币的方法(拼凑m元钱的方案中减去最后一种钱币金额,确保最后一种金额只能为第n中钱币,即val[n]) + +==>递推技巧:用容斥原理对最后一种钱币讨论:(使用了第n种钱币/没有使用第n种钱币)拼凑了m元钱 + + + +lADPDhYBOei5ckPNA3_NBQA_1280_895.jpg_720x720g + + + + + +## 5.墙壁涂色 + + + +![lALPDhYBOeiGuN3NB-7NC1w_2908_2030.png_720x720g-1](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/lALPDhYBOeiGuN3NB-7NC1w_2908_2030.png_720x720g-1.jpg) + + + +### 方法1 + +技巧:先按照非环情况做,保证所有方案之间,相邻墙壁颜色不同,最后再保证首尾颜色不同 + +#### 状态定义 + +$f(n, i, j)$ 代表 n 块墙壁,第一块涂颜色 i,最后一块涂颜色 j 的方案总数 + +#### 递推公式 + +$f(n, i, j) = \sum_{k}{f(n-1,i,k)}\ |\ k \ne j$ + + + +lADPDhJzu0Qm8ZvNA3_NBQA_1280_895.jpg_720x720g + +--- + + + +### 方法2 + +基于第一种做法,优化状态定义,忽略第一块的颜色 + +$f(n, 2, 3) == f(n, 1 , 3)$,即可以忽略i这个变量,变成隐藏的变量 + +#### 状态定义 + +$f(n, j)$ 代表 n 块墙壁,第一块涂颜色 0,最后一块涂颜色 j 的方案总数,总方案数:$f(n,j)*3$ + +#### 递推公式 + +$f(n, j) = \sum_{k}{f(n-1,k)}\ |\ k \ne j$ + +==>$(f(n,1) + f(n,2))*3 = f(n,1) * 6$ + +lADPDgfLP1XmGrbNA3_NBQA_1280_895.jpg_720x720g + + + + + + + + + +--- + + + +### 方法3 + +单刀直入,直接定义状态,求什么定义什么 + +#### 状态定义 + +$f(n)$ 代表 n 块墙壁,首尾颜色不同的方法总数 + +#### 递推公式 + +$f(n) = f(n-1) + 2 \times f(n-2)$ + + + +lADPDg7mPJ-KA9LNA3_NBQA_1280_895.jpg_720x720g + + + +1,3不同的方案总数,1,3不同,4颜色一定,3属于正常状态,所以1,3不同的方案总数为$1 \times f(n-1) = f(n-1)$ + +1和3颜色不同的方法,1,3颜色不同 ==>1,2相同颜色,即1,3颜色不同的方案总数等于1,2相同颜色的方案总数;1,2不同的方案总数为$f(n-2)$,由于4有两种方案,计算总方案数时需要乘以2 + +总方案数$f(n)=1\times f(n-1)+2\times f(n-2)$ + + + +## 4.代码演示 + + + +![截屏2021-01-04 上午10.34.06](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-01-04%20%E4%B8%8A%E5%8D%8810.34.06.png) + + + +$f(n)=f(n-1)\times (k-2) + (k-1) \times f(n-2)$ + +(1,3不同+1,3相同) + +```cpp +#include +#include + +using namespace std; +//定义一个大整型数据结构 +class BigInt : public vector { +public : + BigInt() { + push_back(0); + } + BigInt(int x) { + push_back(x); + process_digit(); + } + BigInt operator*(int x) { + BigInt ret(*this); + ret *= x; + return ret; + } + void operator*=(int x) { + for (int i = 0; i < size(); i++) at(i) *= x; + process_digit(); + return ; + } + void operator+=(const BigInt &num) { + for (int i = 0; i < num.size(); i++) { + if (i == size()) push_back(num[i]); + else at(i) += num[i]; + } + process_digit(); + return ; + } + + BigInt operator+(const BigInt &num) { + BigInt ret(*this); + ret += num; + return ret; + } + + void process_digit() { + for (int i = 0; i < size(); i++) { + if (at(i) < 10) continue; + if (i + 1 == size()) push_back(0); + at(i + 1) += at(i) / 10; + at(i) %= 10; + } + return ; + } +}; + + + +ostream &operator<<(ostream &out, const BigInt &num) { + for (int i = num.size() - 1; i >= 0; --i) { + out << num[i]; + } + return out; +} + +int main() { + int n, k;//n块墙壁k种颜色 + cin >> n >> k; + BigInt f[3] = {0}; + f[1] = k;//有一块墙壁,,k种颜色,k>=1 + f[2] = k * (k - 1);//有两块墙壁,k种颜色,k>=2 + f[0] = k * (k - 1) * (k - 2);//有三块墙壁,k>=3 + + for (int i = 4; i <= n; i++) { + f[i % 3] = f[(i - 1) % 3] * (k - 2) + f[(i - 2) % 3] * (k - 1); + } + + cout << f[n % 3] << endl; + return 0; +} +``` + + + +# 3.从递推到动归(下) + +> 2021.1.4 + + + +## 1.数字三角形 + + + +截屏2021-01-04 下午12.32.26 + + + + + +截屏2021-01-04 下午12.32.26 + + + + + +截屏2021-01-04 下午12.32.59 + + + +截屏2021-01-04 下午12.45.491 + + + + + +0012 + + + + + +### 惊人的发现 + +$f(i, j)$ 代表从底边走到 i, j 点的最大值 + +$f(i, j)$ 代表从顶点走到 i, j 点的最大值 + +1. 数学符号完全一致 +2. 语义信息不同 +3. 递归公式不同 +4. 结论:数学符号无法完全代表状态定义 + + + +### 两种方法的对比 + +本质:两种状态定义方式的对比 + +1. 第一种:不用做边界判断,最终结果,直接存储在 $f[0][0]$ +2. 第二种:需要做边界判断,最终结果,存储在一组数据中 +3. 结论:第一种要比第二种优秀 + + + +## 2.数学归纳法 + +用数学归纳法验证动态规划是否是正确的 + +截屏2021-01-04 下午3.41.45 + + + + + +## 3.动态规划问题的求解套路 + +截屏2021-01-04 下午3.49.09 + +1. 第一步:确定动归状态 +2. 第二步:推导状态转移方程,理解:转移、决策 +3. 第三步:正确性证明,利用数学归纳法 +4. 第四步:程序实现 +5. 所谓的转移,把所有决定 $f(i, j)$ 最优值的状态,放入到决策过程中。 + + + +## 4.递推问题求解方向 + + + +1.我从哪里来:一般用for循环求解 + +例如:数字三角形、兔子繁殖问题、钱币问题、墙壁涂色... + +2.我到哪里去:递归 + +例如:杂务(P1113)、神经网络(P1038)、旅行计划(P1137)…(P洛谷) + + + +### 补充:拓扑序 + +图形结构是最最抽象的数据结构,必须理解成思维逻辑结构 + +1. 拓扑序是一种图形结构上的依赖顺序,一个图的拓扑序不唯一 +2. 拓扑序的本质作用:是把图形结构上变成一个一维序列 +3. 图形结构不能用循环遍历的,一维序列可以 +4. 所有递推问题中的状态更新过程,本质上满足拓扑序 + + + +截屏2021-01-04 下午4.28.08 + + + +截屏2021-01-04 下午4.28.17 + +## 5.例题1:最长上升子序列 + +![截屏2021-01-04 下午4.34.45](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-01-04%20%E4%B8%8B%E5%8D%884.34.45.png) + +![截屏2021-01-04 下午4.41.03](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-01-04%20%E4%B8%8B%E5%8D%884.41.03.png) + + + +### 状态定义 + +$f(i)$ 代表以为 i 为结尾的,最长上升子序列的长度 + +### 状态转移方程 + +$f(i) = max\left\{f(j)\right\} + 1 | j < i, val[j] < val[i] $ + +状态转移的时间复杂度:$O(n^2)$ + + + +后续重点:优化转移过程 + +### 代码演示 + +```cpp +#include +#define MAX_N 1000000 +using namespace std; + +int arr[MAX_N + 5];//子序列 +int dp[MAX_N + 5];//记录上升子序列最长长度 + +int main() { + int n; + scanf("%d", &n); + for (int i = 0; i < n; i++) { + scanf("%d", arr + i); + } + int ans = 0;//记录最长长度 + for (int i = 0; i < n; i++) { + dp[i] = 1; + for (int j = 0; j < i; j++) {//在子序列中寻扎比他小的值 + if (arr[j] >= arr[i]) continue; + dp[i] = max(dp[i], dp[j] + 1); + cout << arr[j] << " "; + } + ans = max(ans, dp[i]); + //cout << "ans = " << ans << endl; + } + cout << ans << endl; + return 0; +} + +``` + + + +[5.3.最长上升子序列优化](#5.3.最长上升子序列优化) + + + + + + + +## 6.例题2:最长公共子序列 + +截屏2021-01-05 上午9.24.57 + +截屏2021-01-05 上午9.27.11 + +### 状态定义 + +$f(i,j)$ 代表第一个字符串取前 i 位,第二个字符串取前 j 位的,最长公共子序列的长度 + +### 状态转移方程 + +$f(i,j) = \left\{\begin{aligned} & max[f(i - 1, j), f(i, j - 1)] &val(i) \neq val(j)\\ & f(i - 1, j - 1) &val(i) = val(j)\end{aligned} \right.$ + +状态转移的时间复杂度:$O(n \times m)$ + + + +学习的重点:注意到,参与决策的状态数量,是会根据条件不同而改变的 + + + +### 代码演示 + + + +```cpp +#include +using namespace std; +#define MAX_N 1000 +string s1, s2; +int dp[MAX_N + 5][MAX_N + 5] = {0}; + +int main() { + cin >> s1 >> s2; + for (int i = 1; i <= s1.size(); i++) { + for (int j = 1; j <= s2.size(); j++) { + dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]); + if (s1[i - 1] == s2[j - 1]) { + dp[i][j] = max(dp[i][j], dp[i - 1][j - 1] + 1); + } + } + } + cout << dp[s1.size()][s2.size()] << endl; + return 0; +} +``` + + + + + +## 7.课后作业题 + +1. **[HZOJ46-切割回文](http://oj.haizeix.com/problem/46)** +2. **[HZOJ47-0/1背包](http://oj.haizeix.com/problem/47)** +3. **[HZOJ48-完全背包](http://oj.haizeix.com/problem/48)** +4. **[HZOJ49-多重背包](http://oj.haizeix.com/problem/49)** + + + +# 4.动归习题 + +## 1.切割回文 + +![截屏2021-01-09 下午2.43.31](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-01-09%20%E4%B8%8B%E5%8D%882.43.31.png) + + + +**状态定义** + +$dp[i]$ 代表取字符串的前 $i$ 位,最少分成多少段回文串 + +**状态转移** + +$dp[i] = min(dp[j]) + 1\ |\ s[j + 1, i]\ is\ palindrome$ + + + +1. 根据状态转移,算法时间复杂度$O(n^2)$ +2. 所以,我们需要对转移阶段进行优化 +3. 动态规划优化章节的时候,重点解决 + + + + + +## 2.切割回文代码实现 + + + +```cpp +#include +#define MAX_N 500000 + +using namespace std; +int dp[MAX_N + 5];//dp[i] 代表取字符串的前i位,最少分成多少段回文串 + +bool is_palindrome(string &s, int i, int j) {//判断i~j是否是回文字符串 + while(i < j) { + if (s[i] - s[j]) return false;//s[i] != s[j] + i++,j--; + } + return true; +} + +int main() { + string s; + cin >> s; + dp[0] = 0; + for (int i = 1; i <= s.size(); i++) { + dp[i] = dp[i - 1] + 1; + for (int j = 0; j < i; j++) { + if (is_palindrome(s, j, i - 1))//是回文串 + dp[i] = min(dp[i], dp[j] + 1); + } + } + cout << dp[s.size()] - 1 << endl; + + + + return 0; +} + +``` + +[5.4切割回文](#5.4切割回文) + +## 3.HZOJ47-0/1背包 + +![截屏2021-01-13 上午11.09.46](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-01-13%20%E4%B8%8A%E5%8D%8811.09.46.png) + + + +**状态定义** + +$dp[i][j]$ 前 i 个物品,背包最大承重为 j 的情况下,得到物品的最大价值 + + + +**状态转移** + +$$dp[i][j] = max\left\{\begin{aligned}&dp[i-1][j]&没选第 i 件\\&dp[i-1][j-v[i]]+w[i] &选了第 i 件\end{aligned}\right.$$ + +𝑉𝑖,𝑊𝑖,分别代表第i件物品的重量𝑉𝑖和价值𝑊𝑖。 + +1. 第一种程序实现,状态如何定义的,程序就如何实现 +2. 第二种程序实现,使用滚动数组,对代码进行了空间优化 +3. 第三种程序实现,将程序中的 $dp$ 数组变成一维的,并且修改了更新顺序 + + + +## 4.0/1背包代码演示 + +### 1.方法一 + +```cpp +#include +using namespace std; +#define MAX_N 100 +#define MAX_V 10000 + +int v[MAX_N + 5], w[MAX_N + 5]; +int dp[MAX_N + 5][MAX_V + 5]; + +int main() { + int V, n; + cin >> V >> n;//背包的最大承重v和物品数n + for (int i = 1; i <= n; i++) + cin >> v[i] >> w[i]; + //物品从第一件开始,输入第i件物品的重量v[i]和价值w[i] + for (int i = 1; i <= n; i++) { + for (int j = 1; j <= V; j++) { + dp[i][j] = dp[i - 1][j]; + if (j >= v[i]) + dp[i][j] = max(dp[i][j], dp[i - 1][j - v[i]] + w[i]); + //前 i 个物品,背包最大承重为 j 的情况下,得到物品的最大价值 + } + } + cout << dp[n][V] << endl; + return 0; +} +``` + + + +### 2.利用滚动数组进行优化 + +减少存储空间 + +```cpp +#include +using namespace std; +#define MAX_N 100 +#define MAX_V 10000 + +int v[MAX_N + 5], w[MAX_N + 5]; +int dp[2][MAX_V + 5]; + +int main() { + int V, n; + cin >> V >> n;//背包的最大承重v和物品数n + for (int i = 1; i <= n; i++) cin >> v[i] >> w[i];//物品从第一件开始,输入第i件物品的重量v[i]和价值w[i] + for (int i = 1; i <= n; i++) { + for (int j = 1; j <= V; j++) { + dp[i % 2][j] = dp[(i - 1) % 2][j]; + //此处只访问了dp[i]和dp[i-1]两行,可以利用滚动数组进行优化 + if (j >= v[i]) dp[i % 2][j] = max(dp[i % 2][j], dp[(i - 1) % 2][j - v[i]] + w[i]); + //dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - v[i]] + w[i]); + //前 i 个物品,背包最大承重为 j 的情况下,得到物品的最大价值 + } + } + cout << dp[n % 2][V] << endl; + return 0; +} +``` + + + +### 3.优化 + +将程序中的 $dp$ 数组变成一维的,并且修改了更新顺序 + +```cpp +#include +using namespace std; +#define MAX_N 100 +#define MAX_V 10000 + +int dp[MAX_V + 5]; + +int main() { + int V, n; //背包的最大承重V和物品数n。 + int v, w; //物品的重量v和价值w + cin >> V >> n; + //for (int i = 1; i <= n; i++) cin >> v[i] >> w[i]; + //物品从第一件开始,输入第i件物品的重量v[i]和价值w[i] + for (int i = 1; i <= n; i++) { + cin >> v >> w; + for (int j = V; j >= v; j--) { + //dp[j] = dp[j]; + //if (j >= v) dp[j] = max(dp[j], dp[j - v] + w); + //前 i 个物品,背包最大承重为 j 的情况下,得到物品的最大价值 + dp[j] = max(dp[j], dp[j - v] + w); + } + } + cout << dp[V] << endl; + return 0; +} +``` + +理解: + +1.j为什么逆序 + +==>需要理解为什么是一维的 + +dp数组经过$dp[j] = max(dp[j], dp[j - v] + w)$后变成了$dp[i][j]$的含义,之前是$dp[i - 1][j]$的含义 + +$dp[j]$更新需要$dp[j]$和$dp[j - v]$的值(相对于二维数组来说,此处需要$dp[i - 1][j]$和$dp[i - 1][j - v]$的值,即上一行的数据),如果从前面开始更新数据,后面需要的数组会被新一层的数据覆盖,所以需要从后面开始更新 + +2.为什么不需要v、w数组 + +读入一件商品,处理一件商品 + +3.dp数组为什么第一维没了 + +dp数组的含义没有变,只是在代码实现中变成了一维的 + + + + + +## 5.海贼 OJ-48-完全背包 + + + +![截屏2021-01-13 下午2.38.25](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-01-13%20%E4%B8%8B%E5%8D%882.38.25.png) + + + + + + + +**状态定义** + +$dp[i][j]$ 前 i 个物品,背包最大承重为 j 的情况下,最大价值 + +**状态转移** + +$$dp[i][j] = max\left\{\begin{aligned}&dp[i-1][j]&没选第 i 件\\&dp[i][j-v[i]]+w[i] &选了若干个第 i 件\end{aligned}\right.$$ + + + +(0/1背包)$dp[i-1][j-v[i]]+w[i] 选了第 i 件$ + +程序实现的时候,参考01背包的程序实现,将逆向刷表,改成正向刷表 + + + +## 6.完全背包代码演示 + + + +```cpp +#include +using namespace std; +#define MAX_N 10000 +int dp[MAX_N + 5]; + +int main() { + int V, n, w, v; + //第一行为两个整数𝑁、𝑉(1≤𝑁,𝑉≤10000),分别代表题目描述中的物品种类数量N和背包容量V。 + cin >> n >> V; + for (int i = 0; i < n; i++) { + cin >> v >> w;//c = v,v = w + //第 𝑖 行两个整数𝐶𝑖、𝑉𝑖,分别代表每种物品的体积和价值。 + for (int j = v; j <= V; j++) { + dp[j] = max(dp[j], dp[j - v] + w); + } + } + cout << dp[V] << endl; + return 0; +} +``` + +此处我们需要正向刷表,因为我们需要$dp[j - v[i]]$能够代表$dp[i][j - v[i]]$的值 + +$dp[i][j-v[i]]+w[i] 选了若干个第 i 件$此处需要的第$i$行的$dp[j-v[i]]$而不是需要第$i - 1$行的$dp[j-v[i]]$ + + + + + +## 7.海贼 OJ-49-多重背包 + + + +![截屏2021-01-13 下午3.17.52](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-01-13%20%E4%B8%8B%E5%8D%883.17.52.png) + + + + + +**问题模型转换** + +1. 多重背包,每类物品多了一个数量限制 +2. 01背包,每种物品只有一个 +3. 将多重背包中的数量限制,当做多个单一物品来处理 +4. 至此就将多重背包,转成了0/1背包问题 + +**状态定义** + +$dp[i][j]$ 前 i 个物品,背包最大承重为 j 的情况下,最大价值 + +**状态转移** + +$$dp[i][j] = max\left\{\begin{aligned}&dp[i-1][j]&没选第 i 件\\&dp[i-1][j-v[i]]+w[i] &选了第 i 件\end{aligned}\right.$$ + + + +## 8.多重背包代码演示 + + + +```cpp +#include +using namespace std; +#define MAX_N 100000 +int dp[MAX_N]; +int main() { + + int V, n, v, w, s; + //第一行输入两个数𝑉、𝑛,分别代表背包的最大承重和物品种类数。 + cin >> V >> n; + for (int i = 0; i < n; i++) { + //𝑉𝑖、𝑊𝑖、𝑆𝑖,分别代表第 𝑖 种物品的重量、价值和数量。 + cin >> v >> w >> s; + for (int k = 0; k < s; k++) { + //对数量分类,对每一种情况进行讨论 + for (int j = V; j >= v; j--) { + dp[j] = max(dp[j], dp[j - v] + w); + } + } + } + cout << dp[V] << endl; + + + return 0; +} + +``` + + + +[5.2.多重背包的优化](#5.2.多重背包的优化) + + + + + + + + + + + + + +## 9.海贼 OJ-50-扔鸡蛋 + +![截屏2021-01-13 下午3.56.18](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-01-13%20%E4%B8%8B%E5%8D%883.56.18.png) + + + + + +**状态定义** + +$dp[n][m]$ 用 n 个鸡蛋,测 m 层楼,最坏情况下最少测$dp[n][m]$次 + + + + + +**状态转移** + +$$dp[n][m] = min(max\left\{\begin{aligned}&dp[n-1][k-1]+1&鸡蛋碎了\\&dp[n][m-k]+1 &鸡蛋没碎\end{aligned})\right.$$ + +楼层数k,最少min,最坏max,对于每一个枚举的k值中选一个值最小的方案,对于每一个确定的k值,都有两种解决方式,在这两种解决方式中取一个最大值 + +1. 程序所使用的存储空间与楼层数量强相关 +2. 楼层数量达到了 $2^{31}$,所以在这种状态定一下不可行 +3. 状态定义不可行,我们就需要优化状态定义 +4. 时间复杂度 $O(n \times m^2)$,当 m 过大的时候,无法通过时间限制 + + + + + +## 10.扔鸡蛋代码演示 + + + +```cpp +#include +using namespace std; + +#define MAX_N 32 +#define MAX_M 100000 +int dp[MAX_N + 5][MAX_M + 5]; +//dp[n][m]用n个鸡蛋,测 m 层楼,最坏情况下最少测dp[n][m]次 + +int main() { + int n, m; + cin >> n >> m; + //输入两个数字 𝑛,𝑚(1≤𝑛≤32,1≤𝑚<231),代表 𝑛 个鸡蛋和 𝑚 层楼。 + for (int i = 0; i <= m; i++) dp[1][i] = i; + //一个鸡蛋测i层楼,有i种方法 + for (int i = 2; i <= n; i++) {//从两个鸡蛋开始 + for (int j = 1; j <= m; j++) {//楼层 + dp[i][j] = j; + for (int k = 1; k <= j; k++) {// + dp[i][j] = min(dp[i][j], max(dp[i - 1][k - 1], dp[i][j - k]) + 1); + } + } + } + cout << dp[n][m] << endl; + + return 0; +} +``` + +30分, + +时间复杂度$O(n \times m^2)$ + +[扔鸡蛋问题优化](##5.1.扔鸡蛋问题的优化) + + + +# 5. 动态规划优化的分类 + +1. 状态转移过程的优化,不改变状态定义,使用一些特殊的数据结构或者算法专门优化转移过程 +2. 程序实现的优化,例如:01背包问题。状态定义没有变、转移过程也没变。 +3. 状态定义的优化,大量训练,才能培养出来的能力,从源头进行优化 +4. 状态定义->源头,转移过程->过程,程序实现->结果 + + + +程序优化:01背包,钱币问题,滚动数组 + + + +## 5.1.扔鸡蛋问题的优化 + + + +### **转移过程优化** + +$$dp[n][m] = min(max\left\{\begin{aligned}&dp[n-1][k-1]+1&鸡蛋碎了\\&dp[n][m-k]+1 &鸡蛋没碎\end{aligned})\right.$$ + +$dp[n][m]$ 用 n 个鸡蛋,测 m 层楼,最坏情况下最少测$dp[n][m]$次 + +通过观察 k 与 $dp[n-1][k-1]$与 $dp[n][m-k]$之间的关系,最优的转移 k 值,一定发生在两个函数的交点处 + +k增加,$dp[n-1][k-1]$增加,$dp[n][m-k]$减小(如果不理解,可以假如用1(n-1=1)个鸡蛋,测m(k - 1 = m)层楼有m种方法理解) + +交叉点出的值满足:$dp[n-1][k-1] \le dp[n][m-k]$ + +![截屏2021-01-13 下午8.39.00](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-01-13%20%E4%B8%8B%E5%8D%888.39.00.png) + + + +m1 k1<=k2 + + + +![截屏2021-01-13 下午8.42.19](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-01-13%20%E4%B8%8B%E5%8D%888.42.19.png) + + + + + + + +![截屏2021-01-13 下午8.48.59](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-01-13%20%E4%B8%8B%E5%8D%888.48.59.png) + + + +优化掉 min 以后,总体时间复杂度变成了 $O(n \times m)$ + + + +```cpp +#include +using namespace std; + +#define MAX_N 32 +#define MAX_M 100000 +int dp[MAX_N + 5][MAX_M + 5]; +//dp[n][m]用n个鸡蛋,测 m 层楼,最坏情况下最少测dp[n][m]次 + +int main() { + int n, m; + cin >> n >> m; + //输入两个数字 𝑛,𝑚(1≤𝑛≤32,1≤𝑚<231),代表 𝑛 个鸡蛋和 𝑚 层楼。 + for (int i = 0; i <= m; i++) dp[1][i] = i;//一个鸡蛋测i层楼,有i种方法 + for (int i = 2; i <= n; i++) {//从两个鸡蛋开始 + dp[i][1] = 1; + int k = 2; + for (int j = 2; j <= m; j++) {//楼层 + + //1. + //while(k < j && dp[i - 1][k - 1] < dp[i][j - k]) ++k; + //dp[i][j] = max(dp[i - 1][k -1], dp[i][j - k]) + 1; + //2. + if(k < j && dp[i - 1][k - 1] < dp[i][j - k]) ++k; + dp[i][j] = max(dp[i - 1][k -1], dp[i][j - k]) + 1; + // while(dp[i - 1][k - 1 + 1] <= dp[i][j - k + 1]) ++k; + // dp[i][j] = dp[i][j - k] + 1; + + } + } + cout << dp[n][m] << endl; + + return 0; +} +``` + +60 + + + + + +### **状态定义的优化** + +1. 原状态定义所需存储空间与 m 相关,m 值域大,所以存不下 +2. 当发现某个自变量与因变量之间存在相关性的时候,两者即可对调 +3. $dp[n][m]=k$ 重定义为$dp[n][k]=m$,代表 n 个鸡蛋扔 k 次,最多测多少层楼 +4. k 的值域小,当 n=2 时,$k \le \sqrt{2m}$ + +![截屏2021-01-14 上午5.57.48](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-01-14%20%E4%B8%8A%E5%8D%885.57.48.png) + + + +![截屏2021-01-14 上午5.58.43](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-01-14%20%E4%B8%8A%E5%8D%885.58.43.png) + + + +**状态转移方程:**$dp[n][k] = dp[n-1][k-1]+dp[n][k-1] + 1$ + +本质上已经不是一个动态规划题目了,实际上变成了一个递推问题 + + + +```cpp +#include +using namespace std; + +#define MAX_N 32 +#define MAX_K 100000 +long long dp[MAX_N + 5][MAX_K + 5]; +//dp[n][k]代表 n 个鸡蛋扔 k 次,最多测多少层楼 + +int solve(int n,int m) { + if (n == 1) return m; + for (int i = 1; i <= MAX_K; i++) dp[1][i] = i;//1个鸡蛋测i层楼有i种方法 + for (int i = 2; i <= n; i++) { + for (int k = 1; k <= MAX_K; k++) { + dp[i][k] = dp[i - 1][k - 1] + dp[i][k - 1] + 1; + } + } + int k = 1; + while(dp[n][k] < m) k++;//n个鸡蛋扔k次不足以测m层楼的时候增加k + + return k; +} + + +int main() { + int n, m; + cin >> n >> m; + //输入两个数字 𝑛,𝑚(1≤𝑛≤32,1≤𝑚<231),代表 𝑛 个鸡蛋和 𝑚 层楼。 + cout << solve(n, m) << endl; + return 0; +} + +``` + +100 + + + + + +动态数组优化存储空间 + +```cpp +#include +using namespace std; + +#define MAX_N 32 +#define MAX_K 100000 +long long dp[2][MAX_K + 5]; +//dp[n][m]用n个鸡蛋,测 m 层楼,最坏情况下最少测dp[n][m]次 + +int solve(int n,int m) { + if (n == 1) return m; + for (int i = 1; i <= MAX_K; i++) dp[1][i] = i;//1个鸡蛋测i层楼有i种方法 + for (int i = 2; i <= n; i++) { + for (int k = 1; k <= MAX_K; k++) { + dp[i % 2][k] = dp[(i - 1) % 2][k - 1] + dp[i % 2][k - 1] + 1; + } + } + int k = 1; + while(dp[n % 2][k] < m) k++;//n个鸡蛋扔k次不足以测m层楼的时候增加k + + return k; +} + + +int main() { + int n, m; + cin >> n >> m; + //输入两个数字 𝑛,𝑚(1≤𝑛≤32,1≤𝑚<231),代表 𝑛 个鸡蛋和 𝑚 层楼。 + cout << solve(n, m) << endl; + return 0; +} +``` + + + + + + + +## 5.2.多重背包的优化 + +[7.海贼 OJ-49-多重背包](#7.海贼 OJ-49-多重背包) + +### 二进制拆分法 + +1. 本质上,对于某一类物品,我们具体要选择多少件,才是最优答案 +2. 普通的单一拆分法,实际上只是想枚举某个物品选择 1--s 件的所有情况 +3. 二进制拆分法可以达到相同的效果,拆分出来的物品数量会更少 +4. 拿14举例,普通拆分法 14 份,二进制拆分法 4 份物品(1 2 4 7) + + + +**时间复杂度:**$O(nm\sum_{i=1}^{i=n}{logs_i})$ + +**最优时间复杂度:**$O(nm)$,借助单调队列,后续再讲 + +**01背包时间复杂度:**$O(nm)$ + +**完全背包时间复杂度:**$O(nm)$ + + + +![截屏2021-01-14 上午7.25.53](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-01-14%20%E4%B8%8A%E5%8D%887.25.53.png) + + + +### 代码演示 + + + +```cpp +#include +using namespace std; +#define MAX_N 100000 +int dp[MAX_N]; +int main() { + + int V, n, v, w, s; + //第一行输入两个数𝑉、𝑛,分别代表背包的最大承重和物品种类数。 + cin >> V >> n; + for (int i = 0; i < n; i++) { + //v、w、s代表物品的重量、价值和数量。 + cin >> v >> w >> s; + for (int k = 1; s; k *= 2) {//当前一共有多少物品 + if (k > s) k = s; + s -= k;//减去物品加上的数量 + for (int j = V; j >= k * v; j--) {//当前这一堆的总重量为k * v + dp[j] = max(dp[j], dp[j - k * v] + k * w); + } + } + } + cout << dp[V] << endl; + + + return 0; +} +``` + + + +## 5.3.最长上升子序列优化 + +[5.例题1:最长上升子序列](#5.例题1:最长上升子序列) + +### 状态定义 + +$dp[i]$,代表以 i 位做为结尾的最长上升子序列的长度 + + + +### 状态转移 + +$dp[i] = max(dp[j]) + 1 | val_j < val_i$ + + + +### 优化方式 + +1. 维护一个单调数组 len,len[i] 代表长度为 i 的序列,结尾最小值 +2. $dp[i]$ 在转移的时候,在 len 数组中查找第一个 $len[k]>=val_i$ 的位置,$dp[i] = k$ +3. 更新 $len[k] = val_i$ +4. 需要明确,len 数组为什么是单调的 +5. 证明过程:假设,更新前是单调的,更新以后,一定是单调的 +6. 在 len 数组中查找位置 k,实际上就是二分算法搞定 + + + +**时间复杂度:**$O(nlogl)$ + + + +![截屏2021-01-14 下午3.06.40](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-01-14%20%E4%B8%8B%E5%8D%883.06.40.png) + +![截屏2021-01-14 下午3.06.51](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-01-14%20%E4%B8%8B%E5%8D%883.06.40.png) + + + + + +### 代码演示 + +```cpp +#include +#include +#define MAX_N 1000000 +using namespace std; + +int len[MAX_N + 5];//长度为i的序列的最小值 +int dp[MAX_N + 5]; + +int binary_search(int *arr, int n, int x) {//二分查找 + //在arr数组中查找第一个大于等于x的值 + int head = 0, tail = n, mid; + while (head < tail) { + mid = (head + tail) >> 1; + if (arr[mid] < x) head = mid + 1; + else tail = mid; + } + return head; +} + +int main() { + + int n, val; + int ans = 0; + scanf("%d", &n); + memset(len, 0x3f, sizeof(len));//极大值 + len[0] = 0;//极小值,0的位置为极小值,其他位置为极大值 + // for (int i = 0; i < n; i++) scanf("%d", val[i]); + for (int i = 1; i <= n; i++) { + cin >> val; + dp[i] = binary_search(len, ans + 1,val);//找到第一个>=val[i]的值 + len[dp[i]] = val; + ans = max(dp[i], ans);//最后一位有记录的下标 + } + + cout << ans << endl; + + return 0; +} +``` + + + +## 5.4切割回文 + +[4.1.切割回文](#1.切割回文) + +提前处理得到 mark 数组,$mark[i]$ 存储的是所有以 i 位置做为结尾的回文串的起始坐标,在转移过程中,利用 mark 数组,就可以避免掉大量的无用循环遍历过程。 + + + +**时间复杂度:**$O(n+m)$,m 是字符串中回文串的数量 + + + +```cpp +#include +#include +#include +#define MAX_N 500000 + +using namespace std; +int dp[MAX_N + 5];//取字符串的前i位,最少分成多少段回文串 +vector mark[MAX_N + 5]; +//以 i 位置做为结尾的回文串的起始坐标 + +int expand(string &s, int i, int j) {//回文字符串 + while (s[i] == s[j]) { + mark[j + 1].push_back(i + 1); + //以j+1为结尾的回文串的起始坐标i+1 + --i, ++j; + if (i < 0 || j >= s.size()) break; + } + return 1; +} + + +int main() { + string s; + cin >> s;//一个长度为n(1≤𝑛≤500000)的字符串S,只包含小写字母。 + dp[0] = 0; + for (int i = 0; s[i]; i++) {//提前处理得到 mark 数组 + expand(s, i, i);//处理奇数类型字符串 + (i + 1 < s.size()) && expand(s, i, i + 1); + //处理偶数类型的字符串 + } + for (int i = 1; i <= s.size(); i++) { + dp[i] = i;//初始化dp[i],取字符串的前i位,最少分成多少段回文串 + for (int j = 0; j < mark[i].size(); j++) { + dp[i] = min(dp[i], dp[mark[i][j] - 1] + 1); + } + } + cout << dp[s.size()] - 1 << endl;//分多少段-1==>切多少刀 + + + + return 0; +} +``` + + + + + + + +# 6.树状数组 + +2021.1.17 20:00 + +## 6.1前缀和与差分 + +### 原数组、前缀和、差分数组 + +1. 原数组:${a_1, a_2,a_3,....,a_n}$ + +2. 前缀和:$S_i=\sum_{k=1}^{k=i}{a_i}$,$a_i=S_i-S_{i-1}$ + +3. 差分数组:$X_i=a_i-a_{i-1}$ + + $X_1 + X_2 + X_3 = (a_1 - a_0) + (a_2 - a_1) + (a_3 - a_2) = a_3 $ + +4. X 数组是 a 数组的差分数组,a 数组是 S 数组的差分数组 + +5. S 数组是 a 数组的前缀和数组,a 数组是 X 数组的前缀和数组 + +6. 前缀和数组以及差分数组,并没有增加信息,只是信息的另外一种表示形式 + +7. 前缀和数组用来优化==区间和==操作 + +8. 差分数组用来优化==区间修改==操作 + + + +### 问题1:原数组区间和操作 + +a 数组上的操作:$O(n)$ + +S 数组上的操作:$O(1),S_i - S_{j-1}=a[j,i]区间和$ + + + +### 问题2:原数组区间元素修改(加法) + +$a=\{a_1,a_2,a_3,a_4,a_5,a_6\}$ + +$X=\{X_1=a_1-a_0,X_2=a_2-a_1,X_3=a_3-a_2,X_4=a_4-a_3,X_5=a_5-a_4,X_6=a_6-a_5\}$ + + + +$\{a_1,a_2+d,a_3+d,a_4+d,a_5,a_6\}$ + +$X=\{X_1=a_1-a_0,X_2=a_2-a_1+d,X_3=a_3-a_2,X_4=a_4-a_3,X_5=a_5-a_4-d,X_6=a_6-a_5\}$ + + + +a 数组时间复杂度:$O(n)$ + +X 数组时间复杂度:$O(1)$ + + + +![截屏2021-01-17 下午8.51.02](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-01-17%20%E4%B8%8B%E5%8D%888.51.02.png) + +## 6.2树状数组 + +![截屏2021-01-17 下午8.54.51](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-01-17%20%E4%B8%8B%E5%8D%888.54.51.png) + + + +1. lowbit 函数求数字 i,二进制表示中的最低1所在的位权 + +2. lowbit(x) = x & -x + + 正数=原码,负数=补码=正数的反码+1, + +3. 树状数组本质上是对前缀和数组的一种优化,主要体现在单点修改操作上 + +4. 前缀和查询 $O(logn)$,单点修改$O(logn)$ + +5. 相比于最普通的前缀和数组,查询方面变差,单点修改操作变好,综合时间复杂度变好 + +6. 查询的时候,向前统计,$i$ 的前一位 $i-lowbit(i)$ + +7. 修改的时候,向后修改,$i$ 的后一位 $i + lowbit(i)$ + +![截屏2021-01-17 下午9.04.29](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-01-17%20%E4%B8%8B%E5%8D%889.04.29.png) + +![截屏2021-01-17 下午9.10.13](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-01-17%20%E4%B8%8B%E5%8D%889.10.13.png) + +![截屏2021-01-17 下午9.10.55](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-01-17%20%E4%B8%8B%E5%8D%889.10.55.png) + +![截屏2021-01-17 下午9.16.13](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-01-17%20%E4%B8%8B%E5%8D%889.16.13.png) + +![截屏2021-01-17 下午9.17.02](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-01-17%20%E4%B8%8B%E5%8D%889.17.02.png) + +![截屏2021-01-17 下午9.37.27](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-01-17%20%E4%B8%8B%E5%8D%889.37.27.png) + + + + + +### 代码实现 + +方便区间修改版 + +```cpp +#define MAX_N 100000 +int c[MAX_N + 5];//维护的是原数组的差分数组 +#define lowbit(x) (x & (-x)) + +void add(int i, int x, int n) {//单点修改 + //在c[n]中的c[i]加x + while (i <= n) { + c[i] += x; + i += lowbit(i); + } + return ; +} + +int query(int i) {//前缀和 + int sum = 0; + while (i) { + sum += c[i]; + i -= lowbit(i); + } + return sum;//原数组前n项和为第i项的值 +} +``` + + + +## 6.3海贼 OJ-329-弱化的整数问题 + +![截屏2021-01-17 下午9.43.34](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-01-17%20%E4%B8%8B%E5%8D%889.43.34.png) + +![截屏2021-01-17 下午9.43.50](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-01-17%20%E4%B8%8B%E5%8D%889.43.50.png) + + + + + +**引入差分数组** + +$a=\{a_1,a_2,a_3,a_4,a_5,a_6\}$ + +$X=\{X_1=a_1-a_0,X_2=a_2-a_1,X_3=a_3-a_2,X_4=a_4-a_3,X_5=a_5-a_4,X_6=a_6-a_5\}$ + + + +$\{a_1,a_2+d,a_3+d,a_4+d,a_5,a_6\}$ + +$X=\{X_1=a_1-a_0,X_2=a_2-a_1+d,X_3=a_3-a_2,X_4=a_4-a_3,X_5=a_5-a_4-d,X_6=a_6-a_5\}$ + + + +引入差分数组 X,将原数组 a 上的区间加操作,转换成 X 数组上的两次【单点操作】 + +对于查询原数组 a[i] 的值,等价于查询 X 数组前 i 位的【前缀和】 + + + + + +**结论** + +由于,既要维护【前缀和】,又要进行【单点修改】,所以可以使用树状数组 + + + +```cpp +#include +using namespace std; +#define MAX_N 100000 +int c[MAX_N + 5];//维护的是原数组的差分数组 +#define lowbit(x) (x & (-x)) + +void add(int i, int x, int n) {//单点修改 + //在c[n]中的c[i]加x + while (i <= n) { + c[i] += x; + i += lowbit(i); + } + return ; +} + +int query(int i) {//前缀和 + int sum = 0; + while (i) { + sum += c[i]; + i -= lowbit(i); + } + return sum;//原数组前n项和为第i项的值 +} + +int main() { + int n, m, pre, a; + cin >> n;//第一行一个整数𝑁,代表序列𝐴的长度 + //第二行是由空格分隔开的𝑁个数,分别代表𝐴1,𝐴2……𝐴𝑛 + pre = 0; + for (int i = 1; i <= n; i++) { + cin >> a;//a代表当前输入的值 + add(i, a - pre, n);//pre代表上一个输出的值 + pre = a; + } + cin >> m;//接下来一行是一个整数𝑚,代表操作的次数。 + char str[10]; + //接下来𝑚行,每行代表这一条指令如题目所述 + int l, r, d, x; + for (int i = 0; i < m; i++) { + cin >> str; + switch (str[0]) { + case 'C': { + // 第一类指令形如𝐶 𝑙 𝑟 𝑑(1≤𝑙≤𝑟≤𝑁),表示把数列中第𝑙...𝑟之间的数都加𝑑(0≤𝑑≤100000) + cin >> l >> r >> d; + add(l, d, n); + add(r + 1, -d, n); + } break; + case 'Q': { + cin >> x;//第二类指令形如𝑄 𝑥(𝑥≤𝑁),表示询问序列中第𝑥个数的值。 + cout << query(x) << endl; + } break; + } + } + + return 0; +} +``` + + + +## 6.4 海贼 OJ-330-加强的整数问题 + +![截屏2021-01-17 下午10.29.57](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-01-17%20%E4%B8%8B%E5%8D%8810.29.57.png) + +![截屏2021-01-17 下午10.30.10](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-01-17%20%E4%B8%8B%E5%8D%8810.30.10.png) + + + +**引入差分数组** + +参考 HZOJ-329 的解法,主要为了维护原数组上的区间修改操作 + + + + + +**原数组上的区间和问题转化** + +$a=\{a_1,a_2,a_3,a_4,a_5,a_6\}$ + +$X=\{X_1=a_1-a_0,X_2=a_2-a_1,X_3=a_3-a_2,X_4=a_4-a_3,X_5=a_5-a_4,X_6=a_6-a_5\}$ + +$Query(l, r) = S(r) - S(l - 1)$,重点分析 S 怎么求,会求 S,万事大吉 + +$S_i= \sum_{k=1}^{i}\sum_{y=1}^{k}{X_y} = \sum_{k=1}{i}{(i + 1)X_k-k*X_k}=(i + 1)\sum_{k=1}^{i}{X_k-\sum_{k=1}^{i}k*X_k}$ + +设$Y_i = i \times X_i$ + +$S_i=(i + 1)\sum_{k=1}^{i}{X_k-\sum_{k=1}^{i}{Y_k}}$ + + + +![截屏2021-01-17 下午10.27.25](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-01-17%20%E4%B8%8B%E5%8D%8810.27.25.png) + + + +**结论** + +$S_i$ 可以通过维护 X 与 Y 两个序列的前缀和得到 + +所以可以通过维护两个与差分数组 X 相关的前缀和数组,从而得到原数组 a 的前缀和值 + +需要维护两个:树状数组 + + + +**代码实现** + +```cpp +#include +using namespace std; +#define MAX_N 100000 +#define lowbit(x) (x & -x) +long long c[2][MAX_N + 5];//差分数组 + +void add(long long k, long long i, long long x, long long n) { + //在c[k][i]的位置加x + while (i <= n) { + c[k][i] += x; + i += lowbit(i); + } + return ; +} + +long long query(long long k, long long i) { + //查询c[k][i]的前缀和 + long long sum = 0; + while (i) { + sum += c[k][i]; + i -= lowbit(i); + } + return sum; +} + +long long S(long long i) {//S[i]前i项和 + return (i + 1) * query(0, i) - query(1, i); +} + +void modify(long long i, long long x, long long n) {//在c[][i]位置上加上x + add(0, i, x, n); + add(1, i, i * x, n); + return ; +} + +int main() { + long long m, n; + //第一行包含两个整数𝑁,𝑀(1≤𝑁,𝑀≤100000),代表序列的长度和询问的次数. + //cin >> n >> m; + scanf("%lld%lld", &n, &m); + //第二行包含𝑁个整数,表示初始的序列𝐴(−1000000000≤𝐴𝑖≤1000000000)。 + for (long long i = 1, pre = 0, a; i <= n; i++) { + //cin >> a; + scanf("%lld", &a); + modify(i, a - pre, n); + pre = a; + } + char s[10]; + for (long long i = 0, l, r, d; i < m; i++) { + //cin >> s; + scanf("%s", s); + switch (s[0]) { + case 'C': {//"C a b c"表示给[a, b]区间中的值全部增加c (-10000 ≤ c ≤ 10000)。 + //cin >> l >> r >> d; + scanf("%lld%lld%lld", &l, &r, &d); + modify(l, d, n); + modify(r + 1, -d, n); + }break; + case 'Q': {//"Q a b" 询问[a, b]区间中所有值的和。 + //cin >> l >> r; + scanf("%lld%lld", &l, &r); + printf("%lld\n", S(r) - S(l - 1)); + //cout << (S(r) - S(l - 1)) << endl; + }break; + } + } + + return 0; +} +``` + + + +# 7.树状数组习题 + +## 一、海贼 OJ-331-丢失的奶牛1 + +1. 理解标记数组,标记数组记录的是每一个下标知否可用,可用为1,不可用为0 +2. 根据题意,我们从后向前,依次确定每一头奶牛的编号 +3. 例如,当前奶牛比他前面的2个奶牛编号大的话,当前奶牛的编号就是当前剩余可用编号中的第三大的编号 +4. 如何找到可用的第 x 大的编号,可以在标记数组的前缀和数组上做二分查找 +5. 设计到标记数组的前缀和维护和单点更新,所以可以使用树状数组 +6. 时间复杂度:$O(nlogn)$ + + + +相似问题:海贼 OJ-332-买票 + +``` + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +using namespace std; +#define MAX_N 80000 +int c[MAX_N + 5]; +#define lowbit(x) (x & -x) +void add(int i, int x, int n) { + while (i <= n) { + c[i] += x; + i += lowbit(i); + } + return ; +} + +int query(int i) { + int sum = 0; + while (i) { + sum += c[i]; + i -= lowbit(i); + } + return sum; +} + +int n; +int cnt[MAX_N + 5]; +int ind[MAX_N + 5]; + +void read() { + cin >> n; + ind[1] = 0; + for (int i = 2; i <= n; i++) cin >> cnt[i]; + for (int i = 1; i <= n; i++) { + add(i, 1, n); + } + return ; +} + +int binary_search(int n, int x) { + int head = 1, tail = n, mid; + while (head < tail) { + mid = (head + tail) >> 1; + if (query(mid) < x) head = mid + 1; + else tail = mid; + } + return head; +} + +void solve() { + for (int i = n; i >= 1; --i) { + ind[i] = binary_search(n, cnt[i] + 1); + add(ind[i], -1, n); + } + return ; +} + +void output() { + for (int i = 1; i <= n; i++) { + cout << ind[i] << endl; + } + return ; +} + +int main() { + read(); + solve(); + output(); + return 0; +} + +``` + + + + + +332 + +``` +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +using namespace std; +#define MAX_N 200000 +int c[MAX_N + 5]; +#define lowbit(x) (x & -x) +void add(int i, int x, int n) { + while (i <= n) { + c[i] += x; + i += lowbit(i); + } + return ; +} + +int query(int i) { + int sum = 0; + while (i) { + sum += c[i]; + i -= lowbit(i); + } + return sum; +} + +int n; +int cnt[MAX_N + 5]; +int val[MAX_N + 5]; +int ind[MAX_N + 5]; +int ans[MAX_N + 5]; + +int binary_search(int n, int x) { + int head = 1, tail = n, mid; + while (head < tail) { + mid = (head + tail) >> 1; + if (query(mid) < x) head = mid + 1; + else tail = mid; + } + return head; +} + +void read() { + cin >> n; + for (int i = 1; i <= n; i++) { + cin >> cnt[i] >> val[i]; + add(i, 1, n); + } + return ; +} + +void solve() { + for (int i = n; i >= 1; --i) { + ind[i] = binary_search(n, cnt[i] + 1); + add(ind[i], -1, n); + ans[ind[i]] = val[i]; + } + return ; +} + +void output() { + for (int i = 1; i <= n; i++) { + i == 1 || cout << " "; + cout << ans[i]; + } + cout << endl; + return ; +} + +int main() { + read(); + solve(); + output(); + return 0; +} + +``` + + + +## 二、海贼 OJ-328-楼兰图腾1 + +1. 求在当前位置之前,小于当前位置值的元素数量,当前元素值记为 X,元素数量记为 a,元素位置记为 i +2. 前面小于 $X$ 的元素数量是 $a$ +3. 后面小于 $X$ 的元素数量是$X - a - 1$ +4. 前面大于 $X$ 的元素数量 $i - a - 1$ +5. 后面大于X 的元素数量$n-X-i+a+1$ +6. 解题关键:前面小于 $X$ 的元素数量是 $a$ +7. 标记数组,记录当前位置之前有哪些元素出现过,出现过标记为 1,否则标记为 0 +8. $a$ 等于标记数组在 $X$ 位置之前的前缀和 +9. 对于标记数组的单点修改及前缀和查询,所以可以使用树状数组 + +``` + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +using namespace std; +#define MAX_N 200000 +long long c[MAX_N + 5]; +#define lowbit(x) (x & -x) +void add(long long i, long long x, long long n) { + while (i <= n) { + c[i] += x; + i += lowbit(i); + } + return ; +} + +long long query(long long i) { + long long sum = 0; + while (i) { + sum += c[i]; + i -= lowbit(i); + } + return sum; +} + +long long n; +long long val[MAX_N + 5]; + +void read() { + cin >> n; + for (long long i = 1; i <= n; i++) { + cin >> val[i]; + } + return ; +} + +void solve(long long &x, long long &y) { + x = y = 0; + for (long long i = 1; i <= n; i++) { + long long a1 = query(val[i]); + long long a2 = val[i] - a1 - 1; + long long b1 = i - a1 - 1; + long long b2 = n - val[i] - b1; + x += b1 * b2; + y += a1 * a2; + add(val[i], 1, n); + } + return ; +} + +int main() { + read(); + long long a, b; + solve(a, b); + cout << a << " " << b << endl; + return 0; +} + +``` + + + +## 三、海贼 OJ-333-区间最大子段和1 + +1. 线段树有点点儿难度的题目 +2. 每个节点:区间和值,最大子段和值,左侧最大子段和,右侧最大子段和 +3. 特殊性质:递归遍历时,是按照下标顺序得到的每一个查询区间内的线段树的节点 +4. $|①②③④⑤|$,就是按照①②③④⑤的顺序遍历得到的每一个节点 +5. 代码有点儿复杂,学会了,代码思维会更上一层楼 + + + +``` + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +using namespace std; +#define MAX_N 500000 +#define L(ind) (ind << 1) +#define R(ind) (ind << 1 | 1) +#define SUM(ind) tree[ind].sum +#define INMAX(ind) tree[ind].inmax +#define LMAX(ind) tree[ind].lmax +#define RMAX(ind) tree[ind].rmax +struct Data{ + int sum, inmax, lmax, rmax; +} _tree[(MAX_N << 2) + 5]; +Data *tree = _tree + 1; +int n, m, flag; +int val[MAX_N + 5]; + +void UP(int a, int b, int c) { + SUM(a) = SUM(b) + SUM(c); + LMAX(a) = max(LMAX(b), SUM(b) + LMAX(c)); + RMAX(a) = max(RMAX(c), SUM(c) + RMAX(b)); + INMAX(a) = max(max(INMAX(b), INMAX(c)), RMAX(b) + LMAX(c)); + return ; +} + +void build(int ind, int l, int r) { + if (l == r) { + SUM(ind) = INMAX(ind) = LMAX(ind) = RMAX(ind) = val[l]; + return ; + } + int mid = (l + r) >> 1; + build(ind << 1, l, mid); + build(ind << 1 | 1, mid + 1, r); + UP(ind, ind << 1, ind << 1 | 1); + return ; +} + +void modify(int ind, int l, int r, int i, int x) { + if (l == r) { + SUM(ind) = INMAX(ind) = LMAX(ind) = RMAX(ind) = x; + return ; + } + int mid = (l + r) >> 1; + if (i <= mid) { + modify(ind << 1, l, mid, i, x); + } else { + modify(ind << 1 | 1, mid + 1, r, i, x); + } + UP(ind, ind << 1, ind << 1 | 1); + return ; +} + +void query(int ind, int l, int r, int x, int y) { + if (x <= l && r <= y) { + if (flag) { + tree[0] = tree[ind]; + flag = 0; + } else { + UP(-1, 0, ind); + tree[0] = tree[-1]; + } + return ; + } + int mid = (l + r) >> 1; + if (x <= mid) { + query(ind << 1, l, mid, x, y); + } + if (y > mid) { + query(ind << 1 | 1, mid + 1, r, x, y); + } + return ; +} + +void solve() { + cin >> n >> m; + for (int i = 1; i <= n; i++) { + cin >> val[i]; + } + build(1, 1, n); + for (int i = 0; i < m; i++) { + int k, x, y; + cin >> k >> x >> y; + switch (k) { + case 1: { + if (x > y) swap(x, y); + flag = 1; + query(1, 1, n, x, y); + cout << INMAX(0) << endl; + } break; + case 2: { + modify(1, 1, n, x, y); + } break; + } + } + return ; +} + +int main() { + solve(); + return 0; +} + +``` + + + + + +# 8.字符串的匹配算法(上) + +## 8.1暴力匹配算法 + +1. 字符串匹配问题:单模匹配问题,顾名思义,只有一个模式串 +2. 依次对齐模式串和文本串的每一位,直到匹配成功 +3. 关键:不重不漏的找到答案 + +![截屏2021-01-18 上午10.36.32](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-01-18%20%E4%B8%8A%E5%8D%8810.36.32.png) + + + +```cpp +using namespace std; + +int brute_force(const char *s, const char *t) { + for (int i = 0; s[i]; i++) { + int flag = 1; + for (int j =0; t[j]; j++) { + if (s[i + j] && s[i + j] == t[j]) continue; + flag = 0; + break; + } + if (flag == 1) return i; + } + return -1; +} + + +int main() { + char s[100], t[100]; + while (scanf("%s%s", s, t) != EOF) { + printf("match(%s, %s) = %d\n", s, t, brute_force(s, t)); + } + + return 0; +} +``` + + + +## 8.2KMP 算法 + +推导**&**构建 **KMP** 算法 **By Hug** (转载时,请注明出处:By Hug) + +曾经自己理解了 KMP 算法以后,跟小伙伴吹牛逼:你信不信,我五分钟给 你讲懂 KMP。事实证明,我失败了。KMP 就是这样的一种存在,理解了思想非 常简单,可是真想讲出来,还给对方讲懂,让对方忘不了,真的是一件困难的事 情。 + +静下心来,反复思索,形成如下推导过程,站在这个点上,我可以跟曾经的 那个小伙伴说一句:我曾经吹的牛逼,自己圆回来,也谢谢你陪我在百度大厦同 住了三个月。 + +经过如下推导过程,没有我曾经说的 5 分钟那么简单,但也绝对没有当初表 述的那么复杂。来吧,我们开始吧。 + +阅读此文之前,请自行阅读如下资料: + +http://blog.csdn.net/yutianzuijin/article/details/11954939 有了以上资料的了解后,让我们一起来开始推导&构建 KMP 算法 + +定义:A 是问题中的模式串(短串),长度为 n 定义:B 是问题中的文本串(长串),长度为 m + +A[i] 代表 A 字符串的第 i 位 + B[j] 代表 B 字符串的第 j 位 + A[i, j] 代表 A 字符串第 i 位到第 j 位,(注:下标从 1 开始) B[i, j] 代表 B 字符串第 i 位到第 j 位,(注:下标从 1 开始) + +Sign(i, j) 为分段函数,取值{0, 1}, 当 A[i] = B[j] 的时候,sign(i, j) = 1 当 A[i] != B[j] 的时候,sign(i, j) = 0 + +重点一:原始问题的等价表示 + +max(∑! 𝑠𝑖𝑔𝑛(𝑖,𝑗+𝑖−1)| 𝑗∈[1,𝑚])?=𝑛 公式1 "#$ + +原始问题转化为如上公式的判定问题,判定最大值公式是否等于 n,公式1 与原问题等价 + +对于公式1做优化,其中 sign 的加和部分没有必要每次都运算 n 次,其实 + +第一次碰到 sign(i, j + i - 1) =0 的时候,就应该停止了。所以,假设,第一次碰到 sign(i, j + i - 1) = 0 的位置时,i 的值是 k + 1,则公式1,简化为如下公式: + +max8∑% 𝑠𝑖𝑔𝑛(𝑖,𝑗+𝑖−1)9 𝑗∈[1,𝑚],𝑘+1位置失配)?=𝑛 公式2 "#$ + +公式1和公式2所求问题等价。 重点二:推导公式**2**等价问题的性质 + +公式2中有两个不定量,j 和 k,j 与文本串有关,k 与答案有关,所以设 置函数 f(j) = k,完成从文本串到答案的映射。 + 原问题变成 max(f(j)) ?= n,与寻找 f(j) 函数的最大值等价 + 设: + +f(j) = k 条件1 + f(j + e) = l 条件2 + 其中,l > k,e 为满足条件的最小正整数,具体含义是,未来能找到一个后面 + +的位置,其匹配成功的长度比之前匹配成功的长度 k 更大。 条件1等价于:A[1,k] = B[j, j + k - 1] + 条件2等价于:A[1, l] = B[j + e, j + e + l - 1] + +将条件2与条件1对齐,看看我们能推出什么样的性质(这个性质,与原 问题不等价,但是不满足这个性质,原问题肯定不成立) + +另:j+e+l–1=j+k–1 得 l=k–e,则有 + +A[1, k - e] = B[j + e, j + k - 1] = A[e + 1, k] + +由此,我们推导得出 f(j) < f(j + e) 的一个重要性质,是通过 A 串来进行表达 的,这个性质就是:**A[1, k - e] = A[e + 1, k]** (也就是通常所说的,前半段等于后 半段) + +由于 e 是满足条件的最小正整数,所以 **A[1, k - e] = A[e + 1, k]** 的含义如下: 1、 描述的物理含义是,前后最长相等的片段。 + 2、当匹配 k+1失败的时候,如果此时 A 串前 k 位匹配成功了,说明从 B + +串的 j + e 位开始匹配,最起码能够匹配成功 k – e 位,那么下一次判断, 应该是用 A[k-e + 1]位 与 B[j + k] 位进行比较。(对于这个性质的理解很 重要,在重点三中会用到) + +重点三:推导 **k** 和 **e** 的映射关系 + +设置函数 g(k)=k–e 等价于A[1,k-e]=A[e+1,k],现在讨论 g(k+1)的值: 操作一:当 A[k–e+1]=A[k+1]时,g(k+1)=g(k)+1 + 操作二:当 A[k–e+1]!=A[k+1]时,说明: + +A[1,k–e]A[k–e+1] 与串 A[e+1,k]A[k+1] 在最后一位上失配了 + +将 A[e + 1, k]A[k + 1]看做是文本串(与 B 串的性质类似),A[1, k - e]A[k – e + 1] 看做模式串,则根据以上推导,在 k – e 位匹配成功,下一位失配的情况下,我 们应该用 A[g(k - e) + 1] (等价与 A[g(g(k)) + 1]) 位与 A[k + 1]进行比较,若相等, 则进行操作一的类似操作,否则继续进行操作二的操作。 + +由于我们的字符串下表是从 1 开始的,所以定义边界条件 g(1) = 0,下标如 果是从 0 开始,则 g(1) = -1 + +对于 g 函数的理解很重要,对于不同的 KMP 算法的实现,其实就是在维护 g 函数中不同的变量值,由于变量值涉及到 k 和 e 两个值,所以相应的,我们可 以定义如下三种不同的 g 函数,对于每一种 g 函数的定义,就对应了不同的 KMP 算法的具体实现: + +g(k) = k - e g(k) = -e g(k) = e + +文章写到这里,我相信你已经具备了自己应用的能力了,得到了 g 函数(所谓的 next 数组)所有的对应关系以后,怎样应用,请回顾重点二中的内容。 + + + +1. KMP 算法中,模式串中的第三部分的重要性 + +2. 第三部分是可以帮助我们加快匹配速度的,避免掉大量无用的匹配尝试 + +3. KMP 算法保证不漏:第三部分匹配到的是模式串的最长前缀 + +4. 普通编码:获得 NEXT 数组,使用 NEXT 数组 + +5. 高级编码:抽象化了一个状态机模型,j 所指向的就是状态机中的位置 + +6. getNext 方法相当于根据输入字符,进行状态跳转,实际上就是改变 j 的值 + + + + + +![截屏2021-01-18 下午4.43.10](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-01-18%20%E4%B8%8B%E5%8D%884.43.10.png) + + + +next数组: + + + +![截屏2021-01-18 下午5.08.27](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-01-18%20%E4%B8%8B%E5%8D%885.08.27.png) + + + +![截屏2021-01-18 下午5.12.55 1](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-01-18%20%E4%B8%8B%E5%8D%885.12.55%201.png) + + + + + +```cpp +#include +#include +using namespace std; + +int brute_force(const char *s, const char *t) { + for (int i = 0; s[i]; i++) { + int flag = 1; + for (int j =0; t[j]; j++) { + if (s[i + j] && s[i + j] == t[j]) continue; + flag = 0; + break; + } + if (flag == 1) return i; + } + return -1; +} + +void getNext(const char *t, int *next) { + //预处理t字符串,储存在next数组中,next数组存储匹配到字符串的下标 + next[0] = -1; + int j = -1;//指向上一位next数组的值 + for (int i = 1; t[i]; i++) {//遍历t字符串 + //如果上一位next数组的值不为-1且当前数组位置的值和字符串不匹配, + while (j != -1 && t[j + 1] != t[i]) j = next[j]; + //字符串j+1位!=当前位置,但是t[j]=t[i - 1], + //如果要使t[j + 1] = t[i],就可以跟着next[j]往回走, + //直到t[j + 1] = t[i]或者没有找到,直到j = -1 + if (t[j + 1] == t[i]) j += 1; + next[i] = j; + } + return ; +} + +int kmp(const char *s, const char *t) { + //初始化next数组 + int n = strlen(t); + int *next = (int *)malloc(sizeof(int) * n + 1); + getNext(t, next); + //输出next数组的值 + /* + for (int i = 0; i < n; i++) { + printf("%d ", next[i]); + } + cout << endl; + */ + //使用next数组进行匹配 + for (int i = 0, j = -1; s[i]; i++) { + //s[i]!=t[j+1] + while (j != -1 && s[i] - t[j + 1]) j = next[j]; + if (s[i] == t[j + 1]) j += 1; + if (t[j + 1] == 0) return i - n + 1; + } + free(next); + return -1; +} + + +int main() { + char s[100], t[100]; + while (scanf("%s%s", s, t) != EOF) { + printf("brute_force(%s, %s) = %d\n", s, t, brute_force(s, t)); + printf("kmp(%s, %s) = %d\n", s, t, kmp(s, t)); + //printf("brute_force(%s, %s) = %d\n", s, t, brute_force(s, t)); + } + + return 0; +} +``` + + + +对代码进行优化 + + + +```cpp +#include +#include + +using namespace std; + +int brute_force(const char *s, const char *t) { + for (int i = 0; s[i]; i++) { + int flag = 1; + for (int j = 0; t[j] && flag; j++) { + flag = flag && (s[i + j] && s[i + j] == t[j]); + } + if (flag == 1) return i; + } + return -1; +} + +int getNext(const char *t, int &j, char input, int *next) { + //传入j和当前位置的字符串 + while (j != -1 && t[j + 1] != input) j = next[j]; + if (t[j + 1] == input) j += 1; + return j; +} + +int kmp(const char *s, const char *t) { + int n = strlen(t); + int *next = (int *)malloc(sizeof(int) * n + 1); + next[0] = -1; + for (int i = 1, j = -1; t[i]; i++) next[i] = getNext(t, j, t[i], next);//初始化 + for (int i = 0, j = -1; s[i]; i++) { + if (getNext(t, j, s[i], next) != n - 1) continue; + //匹配成功 + return i - n + 1;//匹配完成 + } + free(next); + return -1; +} + + +int main() { + char s[100], t[100]; + while (scanf("%s%s", s, t) != EOF) { + printf("brute_force(%s, %s) = %d\n", s, t, brute_force(s, t)); + printf("kmp(%s, %s) = %d\n", s, t, kmp(s, t)); + + } + return 0; +} + +``` + + + +## 8.3SUNDAY 算法 + +1. SUNDAY 算法理解的核心,在于理解黄金对齐点位 +2. 是文本串的匹配尾部,一定会出现在模式串中的字符 +3. 应该和模式串中最后一位出现该字符的位置对齐 +4. 第一步:预处理每一个字符在模式串中最后一次出现的位置 +5. 第二步:模拟暴力匹配算法过程,失配的时候,文本串指针根据预处理信息向后移动若干位 + + + + + +![截屏2021-01-18 下午9.06.30](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-01-18%20%E4%B8%8B%E5%8D%889.06.30.png) + +![截屏2021-01-18 下午9.06.51](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-01-18%20%E4%B8%8B%E5%8D%889.06.51.png) + +![截屏2021-01-18 下午9.07.10](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-01-18%20%E4%B8%8B%E5%8D%889.07.10.png) + +![截屏2021-01-18 下午9.30.57](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-01-18%20%E4%B8%8B%E5%8D%889.30.57.png) + +![截屏2021-01-18 下午9.30.43](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-01-18%20%E4%B8%8B%E5%8D%889.30.43.png) + + + + + + + + + + + +```cpp +#include +#include +using namespace std; +int brute_force(const char *s, const char *t) { + for (int i = 0; s[i]; i++) { + int flag = 1; + for (int j = 0; t[j] && flag; j++) { + flag = flag && (s[i + j] && s[i + j] == t[j]); + } + if (flag == 1) return i; + } + return -1; +} + +int sunday(const char *s, const char *t) { + int offset[256]; + int n = strlen(t), m = strlen(s); + //第一次匹配的时候会从t字符串末尾开始匹配 + //初始化offset数组, + for (int i = 0; i < 256; i++) offset[i] = n + 1; + //假如所有字符都没没有出现过, + for (int i = 0; t[i]; i++) offset[t[i]] = n - i; + //遍历t字符串,字符串出现在倒数第n-i位 + for (int i = 0; i + n <= m; i += offset[s[i + n]]) { + int flag = 1; + for (int j = 0; t[j] && flag; j++) { + flag = flag && (s[i + j] == t[j]); + } + if (flag) return i; + } + return -1; +} + + +int main() { + char s[100], t[100]; + while (scanf("%s%s", s, t) != EOF) { + printf("brute_force(%s, %s) = %d\n", s, t, brute_force(s, t)); + printf("sunday(%s, %s) = %d\n", s, t, sunday(s, t)); + } + + return 0; +} +``` + + + +```shell +brute_force(123456789, aeadaeadaeae) = -1 +sunday(123456789, aeadaeadaeae) = -1 +brute_force(hello, ll) = 2 +sunday(hello, ll) = 2 +brute_force(world, ld) = 3 +sunday(world, ld) = 3 +brute_force(haizei, hu) = -1 +sunday(haizei, hu) = -1 +``` + + + + + +# 9 字符串匹配算法(中) + +## 9.1字符串的哈希匹配算法 + +### HAIZEIOJ-275.兔子与兔子 + +![截屏2021-01-19 下午2.16.17](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-01-19%20%E4%B8%8B%E5%8D%882.16.17.png) + +![截屏2021-01-19 下午2.16.34](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-01-19%20%E4%B8%8B%E5%8D%882.16.34.png) + + + + + + + +1. 可以使用哈希操作判断两个字符串是否相等 +2. 哈希值不同的话,两个字符串一定不相等,从而就不需要按位比较了 +3. $H = (\sum_{k=0}^{n-1}{C_k\times base^k})\%P$ +4. 在文本串上,每一位字符串哈希值的前缀和,方便以后求区间和 +5. $H(i,j)=(HS_j-HS_{i-1})\times (base^i)^{-1}\%P $//求i~j的哈希值 + + + +### 快速求逆元的推导过程 + +$$ +\begin{aligned} +x\times x^{-1}&\equiv1\ (mod\ P) \\ +令:P\%x&=r \\ +P &= kx+r \\ +kx+r &\equiv0\ (mod\ P) \\ +kr^{-1}+x^{-1} &\equiv0\ (mod\ P) \\ +x^{-1} &\equiv-kr^{-1}\ (mod\ P) +\end{aligned} +$$ + + + +### 哈希值的计算 + +![](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-01-19%20%E4%B8%8B%E5%8D%882.36.27.png) + + + +逆元 + +![截屏2021-01-19 下午2.41.05](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-01-19%20%E4%B8%8B%E5%8D%882.41.05.png) + + + +逆元的推导:* + +r=P%x + +![截屏2021-01-19 下午2.41.13](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-01-19%20%E4%B8%8B%E5%8D%882.41.13.png) + + + + + +```cpp +#include +using namespace std; +int inv[7] = {0}; +//求1~7的7的逆元 + +int main() { + inv[1] = 1; + for (int i = 2; i < 7; i++) {//P = 7 + //inv[i] = (-(7/i) * inv[7 % i]);==>是负数,将负数变成正数 + inv[i] = ((-(7 / i) * inv[7 % i]) % 7 + 7) % 7; + cout << i << ":" << inv[i] << endl; + } + + return 0; +} +``` + +```bash +2:4 +3:5 +4:2 +5:3 +6:6 +``` + + + +## 9.2 shift_and 算法 + +1. 第一步对模式串做特殊处理,把每一种字符出现的位置,转换成相应的二进制编码 +2. 后续匹配的过程中跟模式串一毛钱关系都没有 +3. $p_i = (p_{i-1}<<1 | 1) \& d[s_i]$ +4. $p_i$第 j 位二进制为1,代表当前位置为结尾,可以匹配成功模式串的第 j 位 + +![截屏2021-01-19 下午8.38.00](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-01-19%20%E4%B8%8B%E5%8D%888.38.00.png) + +![截屏2021-01-19 下午2.46.34](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-01-19%20%E4%B8%8B%E5%8D%882.46.34.png) + + + +i = 1,P = 010000 + +i = 4, P = 010010 + + + +```cpp +#include +using namespace std; +int brute_force(const char *s, const char *t) {//暴力匹配算法 + for (int i = 0; s[i]; i++) { + int flag = 1; + for (int j = 0; t[j] && flag; j++) { + flag = flag && (s[i + j] && s[i + j] == t[j]); + } + if (flag == 1) return i; + } + return -1; +} + +int shift_and(const char *s, const char *t) { + int d[256] = {0}, n = 0;//处理模式串 + for (int i = 0; t[i]; n++,i++) d[t[i]] |= (1 << i); + int p = 0; + for (int i = 0; s[i]; i++) { + p = (p << 1 | 1) & d[s[i]]; + if (p & (1 << (n - 1))) return i - n + 1;//判断p的第n位是否为1,是:完全匹配成功 + } + return -1; +} + +int main() { + char s[100], t[100]; + while (scanf("%s%s", s, t) != EOF) { + printf("brute_force(%s, %s) = %d\n", s, t, brute_force(s, t)); + printf("shitf_and(%s, %s) = %d\n", s, t, shift_and(s, t)); + } + + + return 0; +} +``` + + + +## 9.3 字典树结构 + +1. 也叫做:前缀索引树 +2. 把每个字符串按照前缀的顺序插入到树形结构中 +3. 字典树可以用于==字符串的排序==,时间复杂度 $O(n)$ + + + +```c +#include +#include +#include +using namespace std; + +#define BASE 26 + +typedef struct Node {//字典树 + int flag;//标记当前结点是否独立成词 + struct Node *next[BASE];//假如只有26个字符串 +} Node; + +Node *getNewNode() { + Node *p = (Node *)malloc(sizeof(Node)); + p->flag = 0; + memset(p->next, 0, sizeof(p->next));//将p的子树的每一个值都初始化为0 + return p; +} + +void insert(Node *p, const char *s) { + for (int i = 0; s[i]; i++) { + int ind = s[i] - 'a'; + if (p->next[ind] == NULL) p->next[ind] = getNewNode(); + p = p->next[ind];//向下走动一个结点,继续向下插入 + } + p->flag = 1; + return ; +} + +void clear(Node *root) { + if (root == NULL) return ; + for (int i = 0; i < BASE; i++) { + clear(root->next[i]); + } + free(root); + return ; +} + +void output(Node *root, int k, char *s) { + s[k] = 0;//字符串的最后一位为0 + if (root->flag) {//当前结点独立成词 + printf("%s\n", s); + } + for (int i = 0; i < BASE; i++) {//遍历子节点 + if (root->next[i] == NULL) continue;//当前子节点为空 + //当前子节点不为空继续向下遍历 + s[k] = 'a' + i; + output(root->next[i], k + 1, s); + } + return ; +} + +int main() { + + int n; + char str[100]; + scanf("%d", &n); + Node *root = getNewNode(); + for (int i = 0; i < n; i++) { + scanf("%s", str); + insert(root, str); + } + output(root, 0, str);//深度优先遍历,当前字典树所在结点地址root,所在层数0, 字符串 + clear(root); + return 0; +} +``` + + + +## 9.4 海贼 OJ-282-最大异或对 + +![截屏2021-01-19 下午11.52.03](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-01-19%20%E4%B8%8B%E5%8D%8811.52.03.png) + +1. 思考:如何使得异或结果尽可能大 +2. 结论:参与异或运算的两个数字,参与异或运算的每一位尽可能不同 +3. 问题转换为:确定一个数字的情况下,找到从高为到低位与当前数字尽量不同的另外一个数字 +4. 把每个数字看成一个二进制字符串,插入到字符串中,采用贪心策略进行选择 + + + +```cpp +#include +using namespace std; +#define MAX_N 310000 +#define BASE 31 + +struct Node { + Node *next[2]; +} tree[MAX_N * BASE + 5];//假如 每个都是32位,所以需要flag + +int cnt = 0; +Node *getNewNode() { + return &tree[cnt++]; +} + +void insert(Node *root, int x) {//插入x + for (int i = 30; i >= 0; i--) { + int ind = !!(x & (1 << i)); + //归一化,将(1 << i) 转换成0或1,(1 << 1) == 2,!!(1 & 2) = 0,!!(2 & 2) = 1 + //ind = (x & (1 << i)) % 2; + if (root->next[ind] == NULL) root->next[ind] = getNewNode(); + root = root->next[ind]; + } +} + +int query(Node *root, int x) { + //求和x所能形成的最大异或和 + int ans = 0; + for (int i = 30; i >= 0; i--) { + int ind = !(x & (1 << i));// + if (root->next[ind]) {//判断第i位异或结果能不能为1 + ans |= (1 << i);//将ans的第i为置为1 + root = root->next[ind]; + } else { + root = root->next[!ind]; + } + } + return ans; +} + +int n; +int val [MAX_N + 5]; + +int main() { + + cin >> n; + int ans = 0; + int a; + Node *root = getNewNode(); + cin >> a; + insert(root, a); + n--; + while (n --) { + cin >> a; + ans = max(query(root, a), ans); + insert(root, a); + } + cout << ans << endl; + + + return 0; +} + +``` + + + +# 10 字符串匹配算法(下) + + + +字典树是字典数据的另一种表现形式,本质字典树+AC自动机s + + + +## 10.1多模匹配问题 + +1. 有多个模式串的匹配问题,就是多模匹配问题 +2. Step1:多个模式串,建立成一棵字典树 +3. Step2:和文本串的每一位对齐匹配,模拟暴力匹配算法的过程 + + + +## 10.2AC 自动机的思想 + +1. 当匹配成功文本串中的 she 时,也就意味着后续一定会匹配成功 he +2. she 对应了字典树中的节点 P,he 对应了字典树中的节点Q +3. P 和 Q 就是等价匹配节点,如果从 P 引出一条边指向 Q,就可以加速匹配过程 +4. 在 P 下面查找节点的操作,等价于在 Q 下面查找节点的操作 +5. 这条等价关系边,通常在 AC 自动机上叫做 【Fail 指针】 +6. AC 自动机 = Trie + Fail 指针(字典树+fai指针) +7. 子节点的 Fail 指针是需要参照父节点的 Fail指针信息的,最简单的建立方式,就是采用【层序遍历】 +8. 没做优化的 AC 自动机,本质上是一个 NFA(非确定型有穷状态自动机) +9. 通俗理解:根据当前状态 p,以及输入字符 c,无法通过一步操作确定状态 +10. 第二种理解:当前状态,并不代表唯一状态。(当前状态、当前状态的fail的状态,当前状态的fail的fail的状态) + + + +**AC 自动机优化:**使用路径压缩思想,使状态转移时可以一步跳转到目标状态。 + + + +优化以后的 AC 自动机,更像 DFA(确定性有穷状态自动机)。 + +![截屏2021-01-24 下午4.30.09](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-01-24%20%E4%B8%8B%E5%8D%884.30.09.png) + + + +## 10.3代码实现 + +```cpp +#include +#include +using namespace std; + +#define BASE 26 + +typedef struct Node { + int flag;//是否独立成词,1是,0不是 + int tag[BASE];//记录每一条边是否被优化过:1字典树中的边,0:ac自动机中的边 + const char *str; + struct Node *next[BASE]; + struct Node *fail;//等价关系指针 +} Node; + +int node_cnt = 0;//记录当前环境一共有多少个节点 + +Node *getNewNode() { + node_cnt += 1; + Node *p = (Node *)malloc(sizeof(Node)); + p->flag = 0; + memset(p->next, 0, sizeof(p->next)); + p->fail = NULL; + return p; +} + +void insert(Node *root, const char *str) {//以字典树的形式插入 + for (int i = 0; str[i]; i++) { + int ind = str[i] - 'a'; + if (root->next[ind] == NULL) root->next[ind] = getNewNode();//要插入的新节点为空 + root->tag[ind] = 1; + root = root->next[ind]; + } + root->flag = 1; + root->str = strdup(str); + return ; +} + +void build_ac(Node *root) {//建立ac自动机 + //用层序遍历的方式遍历,建立fail指针,层序遍历使用队列 + Node **q = (Node **)malloc(sizeof(Node *) * (node_cnt + 5)); + int head = 0, tail = 0; + root->fail = NULL;//初始化根节点fail指针 + for (int i = 0; i < BASE; i++) {//初始化其他节点 + if (root->next[i] == NULL) { + root->next[i] = root;//根节点的next为空,默认指向根节点 + continue; + } + root->next[i]->fail = root;//根节点下面的第一层fail默认指向根节点 + q[tail++] = root->next[i]; + } + while (head < tail) {//当队列不为空 + Node *p = q[head++]; + for (int i = 0; i < BASE; i++) { + Node *c = p->next[i], *k = p->fail; + if (c == NULL) {//当前节点没有子孩子 + p->next[i] = k->next[i];//没有子孩子,直接指向fail指向的节点 + continue; + } + while (k && k->next[i] == NULL) k = k->fail;//fail不为空且没有子孩子则直接指向fail + if (k == NULL) k = root; + if (k->next[i]) k = k->next[i];//k->next[i] != NULL,fail指向的节点那个节点和c指向的节点对应 + c->fail = k; + q[tail++] = c;//c节点压入队列 + } + } + free(q); + return ; +} + +void match(Node *root, const char *text) {//AC自动机的匹配过程 + Node *p = root;//当前状态的位置 + Node *q;//下一次要跳转的状态 + // for (int i = 0; text[i]; i++) {//遍历文本串的每一位 + // int ind = text[i] - 'a'; + // while (p && p->next[ind] == NULL ) p = p->fail;//找不到文本串的内容,就通过fail指针向上跳 + // if (p == NULL) p = root;; + // if (p->nex[ind]) p = p->ext[ind]; + // q = p; + // while (q) {//当前的是fail对应的指针独立成词 + // if (q->flag == 1) printf("find : %s\n", q->str);//输出 + // q = q->fail; + // } + // if (q->flag == 1) printf("find : %s\n", q->str);//p所在的节点独立成词 + // } + // 优化,将空节点指向fail + for (int i = 0; text[i]; i++) {//遍历文本串的每一位 + int ind = text[i] -'a'; + p = p->next[ind]; + q = p; + while (q) { + if (q->flag == 1) printf("find : %s\n", q->str); + q = q->fail; + } + } + return ; +} + +void clear(Node *root) { + if (root == NULL) return ; + for (int i = 0; i < BASE; i++) { + if (root->tag[i]) clear(root->next[i]);//是字典树上的边 + } + free(root); + return ; +} + + +int main() { + int n; + char str[100]; + scanf("%d", &n); + Node *root = getNewNode(); + for (int i = 0; i < n; i++) { + scanf("%s", str); + insert(root, str); + } + build_ac(root);//建立AC自动机 + printf("build ac\n"); + scanf("%s", str); + match(root, str);//匹配文本串 + clear(root); + return 0; +} +``` + +输入 + +``` +5 +say +she +shr +he +her +sasherhs +``` + + + +输出 + +``` +build ac +find : she +find : he +find : he +``` + + + +## 10.4字符串统计 + +![截屏2021-01-25 下午5.01.20](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-01-25%20%E4%B8%8B%E5%8D%885.01.20.png) + + + +![截屏2021-01-25 下午5.01.42](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-01-25%20%E4%B8%8B%E5%8D%885.01.42.png) + +1. AC 自动机裸题 + +2. 解题的关键,在于如何维护每一个单词的计数量 + +3. 使用幼儿园必知必会的指针技巧维护的 + + + + +```cpp + +#include +#include + +#define BASE 26 +#define MAX_N 20000 + +struct Node { + int flag, *cnt;//是否独立成词,指针ans中的某个元素的位置 + int next[26], fail;//26条边,英文单词有26个,存储单词所在数组的下标编号 +} tree[MAX_N + 5]; + +int que[MAX_N + 5], head, tail;//队列 +int *ans[MAX_N + 5];//ans[i]:第i个单词出现的次数 +int root = 1, cnt = 2;//根节点编号,当前可用节点编号 +char str[100005];//文本串,即字符串 S,仅由小写字母组成,长度不超过 10^5,表示蒜头君看的文章 +int n; + +int getNewNode() { return cnt++; } +int *insert(const char *str) {//在字典树中插入单词 + int p = root;//p指向根节点 + for (int i = 0; str[i]; i++) { + int ind = str[i] - 'a';//第i位所对应根的编号 + if (tree[p].next[ind] == 0) tree[p].next[ind] = getNewNode();//p节点的第ind条边 + p = tree[p].next[ind]; + } + tree[p].flag = 1;// + if (tree[p].cnt == NULL) { + tree[p].cnt = (int *)malloc(sizeof(int)); + tree[p].cnt[0] = 0; + } + return tree[p].cnt; +} + +void build() {//建立fail指针 + //初始化队列 + head = tail = 0; + tree[root].fail = 0;//初始化根节点的fail指针 + for (int i = 0; i < BASE; i++) {//初始化根节点的子孩子 + if (tree[root].next[i] == 0) {//没有第i个子孩子,直接指向根节点 + tree[root].next[i] = root; + continue; + } + tree[tree[root].next[i]].fail = root;//根节点的第i个子孩子的fail指针 + que[tail++] = tree[root].next[i];//入队列 + } + while (head < tail) {// + int p = que[head++];//取出当前节点 + for (int i = 0; i < BASE; i++) {//扫描当前节点的子孩子,建立fail指针 + int c = tree[p].next[i], k = tree[p].fail; + if (c == 0) { + tree[p].next[i] = tree[k].next[i]; + continue; + } + + k = tree[k].next[i]; + tree[c].fail = k; + que[tail++] = c; + } + } + return ; +} + +void match(const char *str) { + int p = root;//当前状态 + for (int i = 0; str[i]; i++) { + int ind = str[i] - 'a', q; + p = tree[p].next[ind]; + q = p; + while (q) { + if (tree[q].flag) {//q节点独立成词 + (*tree[q].cnt) += 1; + } + q = tree[q].fail; + } + } + return ; +} + +int main() { + scanf("%d", &n);//第一行输入一个整数 n(10001≤n≤1000),表示蒜头君学习的 n 个单词。 + for (int i = 0; i < n; i++) {//接下来 n 行,每行输入一个字符串,仅由小写字母组成,长度不超过 20。 + scanf("%s", str); + ans[i] = insert(str); + } + scanf("%s", str);//文本串,输入一个字符串 S,仅由小写字母组成,长度不超过 10^5,表示蒜头君看的文章。 + build(); + match(str); + for (int i = 0; i < n; i++) { + printf("%d: %d\n", i, *ans[i]); + } + return 0; +} + +``` + + + + + + + + + + + + + + + + + + + + + +# 0.END + + + + + + + + + + + + + + + + + + + + + + + + + diff --git "a/02.c++\347\254\224\350\256\260/06.\347\275\221\347\273\234\347\274\226\347\250\213\345\222\214\351\253\230\347\272\247\347\263\273\347\273\237\347\274\226\347\250\213.md" "b/02.c++\347\254\224\350\256\260/06.\347\275\221\347\273\234\347\274\226\347\250\213\345\222\214\351\253\230\347\272\247\347\263\273\347\273\237\347\274\226\347\250\213.md" new file mode 100644 index 0000000..d0f0032 --- /dev/null +++ "b/02.c++\347\254\224\350\256\260/06.\347\275\221\347\273\234\347\274\226\347\250\213\345\222\214\351\253\230\347\272\247\347\263\273\347\273\237\347\274\226\347\250\213.md" @@ -0,0 +1,4440 @@ +\--- + +title: 网络编程 + +date: 2020-12-29 08:08:15 + +tags: 网络编程 + +categories: 网络编程 + +\--- + + + +> 2020.12.29 +> +> 说明:本文档的部分图片来至于开课吧宿船长PPT + +# 一、系统编程 + +# 1.Linux下命令行解析 + + + +## 1.getopt函数 + +截屏2021-01-05 上午9.48.49 + + + +![截屏2021-01-05 上午9.58.40](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-01-05%20%E4%B8%8A%E5%8D%889.58.40.png) + + + +![截屏2021-01-05 上午9.58.59](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-01-05%20%E4%B8%8A%E5%8D%889.58.59.png) + + + +## 2.getopt函数代码演示 + +### 演示1 + +```c +#include +#include +#include + +int main(int argc, char **argv) { + int opt; + while ((opt = getopt(argc, argv, "al")) != -1) { + //循环把参数读入opt中,直到函数返回值为-1 + switch (opt) { + case 'a': + printf("a found!\n"); + break; + case 'l': + printf("l found!\n"); + break; + default: + fprintf(stderr, "Usage : %s -al\n", argv[0]); + exit(-1); + } + } + return 0; +} +``` + + + + + +编译后运行结果 + +```c +./a.out ls -al -d +a found! +l found! +./a.out: invalid option -- 'd' +Usage : ./a.out -al +``` + + + +### 演示2:可选和不可选参数 + +```cpp +#include +#include +#include + +int main(int argc, char **argv) { + int opt; + while ((opt = getopt(argc, argv, "alm:o::")) != -1) { + switch (opt) { + case 'a': + printf("a found!\n"); + break; + case 'l': + printf("l found!\n"); + break; + case 'm': + printf("msg = %s \n", optarg); + break; + case 'o': + printf("opt = %s\n", optarg); + break; + default: + fprintf(stderr, "Usage : %s -al\n", argv[0]); + exit(-1); + } + } + return 0; +} +``` + + + +```shell +#运行结果 +./a.out -m "lsm1" -m"lsm2" -o"lso1" -o "lso2" +msg = lsm1 +msg = lsm2 +opt = lso1 +opt = (null) +#加入m后面没有参数 +./a.out -m +./a.out: option requires an argument -- 'm' +Usage : ./a.out -al +``` + + + + + +## 3.extern + + + +## 4.optind,optopt + +```cpp +#include +#include + +int main(int argc, char **argv) { + printf("optind = %d \noptopt = %d\n", optind, optopt); + return 0; +} +``` + + + +```zsh +#运行结果 +optind = 1 +optopt = 0 +``` + + + + + + + +# 2.文件与IO + +## 0.概述 + +>所有执行 I/O 操作的系统调用都以文件描述符,一个非负整数(通常是小整数),来指代 打开的文件。文件描述符用以表示所有类型的已打开文件,包括管道(pipe)、FIFO、socket、终端、 设备和普通文件。针对每个进程,文件描述符都自成一套。 + + + +>按照惯例,大多数程序都期望能够使用 3 种标准的文件描述符,见表 4-1。在程序开始运 行之前,shell 代表程序打开这 3 个文件描述符。更确切地说,程序继承了 shell 文件描述符的 副本—在 shell 的日常操作中,这 3 个文件描述符始终是打开的。(在交互式 shell 中,这 3 个文件描述符通常指向 shell 运行所在的终端。)如果命令行指定对输入/输出进行重定向操作, 那么 shell 会对文件描述符做适当修改,然后再启动程序。 + +![截屏2021-01-07 下午3.51.09](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-01-07%20%E4%B8%8B%E5%8D%883.51.09.png) + + + + + +下面介绍执行文件 I/O 操作的 4 个主要系统调用(编程语言和软件包通常会利用 I/O 函数 库对它们进行间接调用)。 + +1. fd = open(pathname, flags, mode) 函数打开 pathname 所标识的文件,并返回文件描 述符,用以在后续函数调用中指代打开的文件。如果文件不存在,open()函数可以 创建之,这取决于对位掩码参数 flags 的设置。flags 参数还可指定文件的打开方式:只 读、只写亦或是读写方式。mode 参数则指定了由 open()调用创建文件的访问权限, 如果 open()函数并未创建文件,那么可以忽略或省略 mode 参数。 + + + +2. numread = read(fd, buffer, count) 调用从 fd 所指代的打开文件中读取至多 count 字节的 数据,并存储到 buffer 中。read()调用的返回值为实际读取到的字节数。如果再无字节 可读(例如:读到文件结尾符 EOF 时),则返回值为 0。 + + + +3. numwritten = write(fd, buffer, count) 调用从 buffer 中读取多达 count 字节的数据写入由 fd 所指代的已打开文件中。write()调用的返回值为实际写入文件中的字节数,且有可 能小于 count。 + + + +4. status = close(fd)在所有输入/输出操作完成后,调用 close(),释放文件描述符 fd 以及 与之相关的内核资源。 + + + + + +## 1.打开一个文件:open函数 + + + +### 1.open简介 + +`int open(const char *pathname, int flags)` + +![截屏2021-01-07 下午3.55.29](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-01-07%20%E4%B8%8B%E5%8D%883.55.29.png) + +​ + +​ 要打开的文件由参数 pathname 来标识。如果 pathname 是一符号链接,会对其进行解引用。 如果调用成功,open()将返回一文件描述符,用于在后续函数调用中指代该文件。若发生错误, 则返回−1,并将 errno 置为相应的错误标志。 + +​ + +​ 参数 flags 为位掩码,用于指定文件的访问模式, + +![截屏2021-01-07 下午3.57.06](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-01-07%20%E4%B8%8B%E5%8D%883.57.06.png) + +​ + +​ 当调用 open()创建新文件时,位掩码参数 mode 指定了文件的访问权限。(SUSv3 规定,mode 的数据类型 mode_t 属于整数类型。)如果 open()并未指定 O_CREAT 标志,则可以省略 mode 参数。 + +​ SUSv3 规定,如果调用 open()成功,必须保证其返回值为进程未用文件描述符中数值最 小者。可以利用该特性以特定文件描述符打开某一文件。 + + + +### 2.open()调用中的 flags 参数 + +![截屏2021-01-07 下午4.07.42](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-01-07%20%E4%B8%8B%E5%8D%884.07.42.png) + +![截屏2021-01-07 下午4.13.14](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-01-07%20%E4%B8%8B%E5%8D%884.13.14.png) + + + +### 3.open()函数的错误 + +若打开文件时发生错误,open()将返回−1,错误号 errno 标识错误原因。以下是一些可能 发生的错误(除了在上节参数描述中已经提及的错误之外)。 + +EACCES + +​ 文件权限不允许调用进程以 flags 参数指定的方式打开文件。无法访问文件,其可能的原 因有目录权限的限制、文件不存在并且也无法创建该文件。 + +EISDIR + +​ 所指定的文件属于目录,而调用者企图打开该文件进行写操作。不允许这种用法。 + + EMFILE + +​ 进程已打开的文件描述符数量达到了进程资源限制所设定的上限 + + ENFILE + +​ 文件打开数量已经达到系统允许的上限。 + +ENOENT + +​ 要么文件不存在且未指定 O_CREAT 标志,要么指定了 O_CREAT 标志,但 pathname 参数所指定路径的目录之一不存在,或者 pathname 参数为符号链接,而该链接指向的文件不存 在(空链接)。 + +EROFS + +​ 所指定的文件隶属于只读文件系统,而调用者企图以写方式打开文件。 + +ETXTBSY + +​ 所指定的文件为可执行文件(程序),且正在运行。系统不允许修改正在运行的程序(比如 以写方式打开文件)。(必须首先终止程序运行,然后方可修改可执行文件。) + + + +### 4.open代码演示 + +> `open, read, write, close` + +```c +#include +#include +#include +#include +#include +#include +#include +int main() { + int fd; + char buff[512] = {0}; + ssize_t nread; + if ((fd = open("./a.txt", O_CREAT | O_RDONLY)) < 0) {//打开文件 + perror("open"); + exit(1);//return 1; + } + while ((nread = read(fd, buff, sizeof(buff))) > 0) {//从fd读入到buff,每次的大小为buff的大小 + printf("read %ld\n buffer = %s", nread, buff); + memset(buff, 0, sizeof(buff)); + } + close(fd); + return 0; +} +``` + +`perror : perror - print a system error message` + +打印系统错误信息 + + + +## 2.读取文件内容:read() + +> read()系统调用从文件描述符 fd 所指代的打开文件中读取数据。 + +![截屏2021-01-07 下午4.15.35](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-01-07%20%E4%B8%8B%E5%8D%884.15.35.png) + +​ count 参数指定最多能读取的字节数。(size_t 数据类型属于无符号整数类型。)buffer 参数提供用来存放输入数据的内存缓冲区地址。缓冲区至少应有 count 个字节。 + +​ 如果 read()调用成功,将==返回实际读取的字节数==,如果遇到文件结束(EOF)则返回 0, 如果出现错误则返回-1。ssize_t 数据类型属于有符号的整数类型,用来存放(读取的)字节数 或-1(表示错误)。 + +​ 一次 read()调用所读取的字节数可以小于请求的字节数。对于普通文件而言,这有可能是 因为当前读取位置靠近文件尾部。 + +​ 当 read()应用于其他文件类型时,比如管道、FIFO、socket 或者终端,在不同环境下也会 出现 read()调用读取的字节数小于请求字节数的情况。 + +![截屏2021-01-07 下午4.22.37](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-01-07%20%E4%B8%8B%E5%8D%884.22.37.png) + +## 3.数据写入文件:write() + +> write()系统调用将数据写入一个已打开的文件中。 + +![截屏2021-01-07 下午4.24.18](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-01-07%20%E4%B8%8B%E5%8D%884.24.18.png) + + + +​ write()调用的参数含义与 read()调用相类似。buffer 参数为要写入文件中数据的内存地址,count 参数为欲从 buffer 写入文件的数据字节数,fd 参数为一文件描述符,指代数据要写入的文件。 + +​ 如果 write()调用成功,将返回实际写入文件的字节数,该返回值可能小于 count 参数值。 这被称为“部分写”。对磁盘文件来说,造成“部分写”的原因可能是由于磁盘已满,或是因 为进程资源对文件大小的限制。 + +​ 对磁盘文件执行 I/O 操作时,write()调用成功并不能保证数据已经写入磁盘。因为为了减 少磁盘活动量和加快 write()系统调用,内核会缓存磁盘的 I/O 操作。 + + + +## 4.关闭文件:close() + +> ​ close()系统调用关闭一个打开的文件描述符,并将其释放回调用进程,供该进程继续使用。 当一进程终止时,将自动关闭其已打开的所有文件描述符。 + +![截屏2021-01-07 下午4.26.46](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-01-07%20%E4%B8%8B%E5%8D%884.26.46.png) + +​ 显式关闭不再需要的文件描述符往往是良好的编程习惯,会使代码在后续修改时更具可 读性,也更可靠。进而言之,文件描述符属于有限资源,因此文件描述符关闭失败可能会导 致一个进程将文件描述符资源消耗殆尽。在编写需要长期运行并处理大量文件的程序时,比 如 shell 或者网络服务器软件,需要特别加以关注。 + + + +小结:为了对普通文件执行 I/O 操作,首先必须调用 open()以获得一个文件描述符。随之使用 read()和 write()执行文件的 I/O 操作,然后应使用 close()释放文件描述符及相关资源。这些系 统调用可对所有类型的文件执行 I/O 操作。 所有类型的文件和设备驱动都实现了相同的 I/O 接口,这保证了 I/O 操作的通用性,同时 也意味着在无需针对特定文件类型编写代码的情况下,程序通常就能操作所有类型的文件。 + + + +## 5.fopen函数 + +> `fopen, fwrite, fread, fclose` + + + +```c +#include +#include +#include + + +int main() { + + FILE *fp = NULL; + if ((fp = fopen("./b.txt", "r")) == NULL) { + perror("fopen");//fopen: No such file or directory + exit(1); + } + while (1) {//循环读入,直到文件结尾 + char buff[512] = {0}; + size_t nread = fread(buff, 1, sizeof(buff), fp); + printf("%s", buff); + if (nread <= 0) break; + } + fclose(fp); + + return 0; +} + +``` + + + +标准IO都是缓冲IO + +标准错误输出都会无缓冲输出 + + + +```c +#include +int main() { + sleep(2); + fprintf(stderr, "Hello world!\n"); + printf("Hello world!\n"); + fflush(stdout); + sleep(2); + sleep(5); + + return 0; +} +``` + +sleep()库函数可将当前执行的进程挂起指定的秒数。 + + + +# 3.阻塞IO与非阻塞IO + +## 1.阻塞和非阻塞的概念 + +> 什么是阻塞、什么是非阻塞 + +阻塞操作是指在执行设备操作时若不能获得资源则挂起进程,直到满足可操作的条件后再进行操作。被挂起的进程进入休眠状态,被从调度器的运行队列移走,直到等待的条件被满足。非阻塞操作的进程在不能进行设备操作时并不挂起,它或者放弃,或者不停地查询,直至可以进行操作为止。 + + + +阻塞就是数据给了内核,内核没有返回结果之前,你必须等待。非阻塞就是数据给了内核,不论结果如何。 + +阻塞的代价:必须等待,等待会占用系统资源 + +非阻塞的优点:不会占用系统资源 + +非阻塞的代价 + + + +## 2.对阻塞非阻塞的理解 + + + +>废话不说,老张爱喝茶,煮开水,有两个水壶,普通水壶,响水壶(水开会提示) + +**同步**就是普通水壶烧开水,要没事儿自己过来来看开没开; +**异步**就是响水壶烧开水,水开了水壶响了通知你。 +**阻塞**是烧开水的过程中,你不能干其他事情(即你被阻塞住了),只能站那等水开; +**非阻塞**是烧开水的过程里可以干其他事情。比如去客厅看看电视; + +> 同步与异步说的是你获得水开了的方式不同。 +> 阻塞与非阻塞说的是你得到结果之前能不能干其他事情。 +> 两组概念描述的是不同的内容。 + +这里你看明白了,就会发现: + +> 效率最高的办法是 **响水壶烧水(异步)** +> **水烧开提示你之前可以去干别的事儿(非阻塞)** +> 等到水开了提示你你再去拿水 +> 所以异步和非阻塞常常在一起大大提高每个线程的效率 + + + +摘自[阻塞](https://blog.csdn.net/evanxuhe/article/details/79627709) + +## 3.非阻塞IO有什么用 + +当我们告诉内核,如果数据没有到来,你立马给我返回,不用等待数据了。设置成非阻塞的方法如下。 + +```c +fcntl(sockfd, F_SETFL, flags | O_NONBLOCK); //设置成非阻塞模式; +``` + +其实非阻塞模式的使用并不普遍,因为非阻塞模式会浪费大量的CPU资源。、 + + + +用户态下沉到内核态 + +内核是一个快缓冲 + +fcntl + + + +## 4.fcntl() + +fcntl()系统调用对一个打开的文件描述符执行一系列控制操作。 + +函数原型 + +![截屏2021-01-08 上午10.55.21](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-01-08%20%E4%B8%8A%E5%8D%8810.55.21.png) + +函数描述 + +![截屏2021-01-08 上午10.55.49](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-01-08%20%E4%B8%8A%E5%8D%8810.55.49.png) + +​ cmd 参数所支持的操作范围很广。 + +​ fcntl()的第三个参数以省略号来表示,这意味着可以将其设置为不同的类型,或者加以省 略。内核会依据 cmd 参数(如果有的话)的值来确定该参数的数据类型。 + + + +​ fcntl()的用途之一是针对一个打开的文件,获取或修改其访问模式和状态标志(这些值是 通过指定 open()调用的 flag 参数来设置的)。要获取这些设置,应将 fcntl()的 cmd 参数设置为 F_GETFL。 + +fcntl函数有5种功能: + +  1.复制一个现有的描述符(cmd=F_DUPFD). + +  2.获得/设置文件描述符标记(cmd=F_GETFD或F_SETFD). + +​ 3.获得/设置文件状态标记(cmd=F_GETFL或F_SETFL). + +​ 4.获得/设置异步I/O所有权(cmd=F_GETOWN或F_SETOWN). + +​ 5.获得/设置记录锁(cmd=F_GETLK,F_SETLK或F_SETLKW). + + + +## 5.fcntl()代码演示 + +### 1.00.head.h + +头文件 + +```c +#include +#include +#include +#include +#include +#include +#include + +#include "05.common.h" +``` + +### 2.04.common.c + +阻塞与非阻塞的实现 + +```c +#include "00.head.h" + + +int make_nonblock(int fd) {//设置非阻塞 + //fcntl(fd, F_SETFL, O_NONBLOCK); + int flag; + if ((flag = fcntl(fd, F_GETFL)) < 0) {//获得文件状态标记 + return -1; + } + flag |= O_NONBLOCK;//flag = O_NONBLOCK,以非阻塞方式打开,在原先flag的基础加上非阻塞方式 + return fcntl(fd, F_SETFL, flag);//设置文件状态标记 +} + +int make_block(int fd) {//设置阻塞方式 + //fcntl(fd, F_SETFL, O_NONBLOCK); + int flag; + if ((flag = fcntl(fd, F_GETFL)) < 0) {//获得文件状态标记flag + return -1; + } + flag &= ~O_NONBLOCK;//flag = O_NONBLOCK,以非阻塞方式打开,设置非阻塞方式 + return fcntl(fd, F_SETFL, flag);//设置文件状态标记 +} + +``` + +### 3.05.common.h + +``` + +#ifdef _COMMON_H +#define _COMMON_H +int make_nonblock(int fd); +int make_block(int fd); +#endif +``` + +### 4.07.testblock.c + +//测试阻塞和非阻塞 + +```c +#include "00.head.h" +//test1 +int make_nonblock(int fd); +int main() { + int age; + make_nonblock(0);//设置当前文件打开方式为非阻塞 + + int ret = scanf("%d", &age); + printf("guziqiu is %d years old!, ret = %d \n", age, ret); + perror("scanf");//打印scanf错误信息 + + return 0; +} + +//输出结果 +guziqiu is -151492080 years old!, ret = -1 +scanf: Resource temporarily unavailable + //sacnf返回值是成功读入变量的个数,-1为报错 + //文件资源占时不可访问 + + //test 2 +int make_nonblock(int fd); +int main() { + int age; + make_nonblock(0);//设置当前文件打开方式为非阻塞 + sleep(5); + int ret = scanf("%d", &age); + printf("guziqiu is %d years old!, ret = %d \n", age, ret); + perror("scanf");//打印scanf错误信息 + + return 0; +} +//输出结果 +18 +guziqiu is 18 years old!, ret = 1 +scanf: Success +``` + + + +## 6.select() + +部分内容来至于[linux select函数解析以及事例](https://zhuanlan.zhihu.com/p/57518857) + +### 6.1select详解 + +IO感知 + +![截屏2021-01-08 下午4.25.54](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-01-08%20%E4%B8%8B%E5%8D%884.25.54.png) + +```c +#include +#include +#include +int select(int nfd, fd_set *readfds, fd_set *writefds, + fd_set *exceptfds, struct timeval *timeout); +/* +nfds:被监听的文件描述符的总数,它比所有文件描述符集合中的文件描述符的最大值大1,因为文件描述符是从0开始计数的; + +readfds、writefds、exceptset:分别指向可读、可写和异常等事件对应的描述符集合。 + +timeout:用于设置select函数的超时时间,即告诉内核select等待多长时间之后就放弃等待。timeout == NULL 表示等待无限长的时间 +*/ +``` + + + +参数说明 + +- nfds:是一个整数值,是指集合中所有文件描述符的范围,即所有文件描述符的最大值加1,不能错。在linux系统中,select的默认最大值为1024。设置这个值的目的是为了不用每次都去轮询这1024个fd,假设我们只需要几个套接字,我们就可以用最大的那个套接字的值加上1作为这个参数的值,当我们在等待是否有套接字准备就绪时,只需要监测maxfd+1个套接字就可以了,这样可以减少轮询时间以及系统的开销。 +- readfds:首先需要明白,fd_set是什么数据类型,有一点像int,又有点像struct,其实,fd_set声明的是一个集合,也就是说,readfs是一个容器,里面可以容纳多个文件描述符,把==需要监视的描述符==放入这个集合中,==当有文件描述符可读时,select就会返回一个大于0的值,表示有文件可读==; +- writefds:和readfs类似,表示有一个==可写的文件描述符==集合,当有文件可写时,select就会返回一个大于0的值,表示有文件可写; +- fd_set*errorfds同上面两个参数的意图,用来==监视文件错误异常文件==。 +- timeout:这个参数一出来就可以知道,可以选择阻塞,可以选择非阻塞,还可以选择定时返回。当将timeout置为NULL时,表明此时select是阻塞的;当将tineout设置为timeout->tv_sec = 0,timeout->tv_usec = 0时,表明这个函数为非阻塞;当将timeout设置为非0的时间,表明select有超时时间,当这个时间走完,select函数就会返回。从这个角度看,个人觉得可以用select来做超时处理,因为你如果使用recv函数的话,你还需要去设置recv的模式,麻烦的很。 + + + +```c +struct timeval{ + + long tv_sec; /*秒 */ + + long tv_usec; /*微秒 */ + + } +``` + +返回值:超时返回0;失败返回-1;成功返回大于0的整数,这个整数表示就绪描述符的数目 + +fd_set的几个宏: + +```c +void FD_ZERO(fd_set *set);//一个 fd_set类型变量的所有位都设为 0 +void FD_SET(int fd, fd_set *set);//清除某个位时可以使用 +void FD_CLR(int fd, fd_set *set); //设置变量的某个位置位 +int FD_ISSET(int fd, fd_set *set);//测试某个位是否被置位 +``` + + + +对fd_set的理解:fd_set可以理解为一个集合,那么集合就会有一个数量,在总定义了一个常量FD_SETSIZE,默认为1024,也就是说在这个集合内默认最多有1024个文件描述符,但是通常你用不了这么多,你通常只是关心nfds个描述符。也就是说你现在有nfds个文件描述符在这个集合里,那么我怎么知道集合里的哪个文件描述符有消息来了呢?你可以将fd_set中的集合看成是二进制bit位,一位代表着一个文件描述符。==0代表文件描述符处于睡眠状态,没有数据到来;1代表文件描述符处于准备状态,可以被应用层处理。==我觉得select函数可以分下面几步进行理解 + +1. 在你开始监测这些描述符时,你先将这些文件描述符全部置为0 +2. 当你需要监测的描述符置为1 +3. 使用select函数监听置为1的文件描述符是否有数据到来 +4. ==当状态为1的文件描述符有数据到来时,此时你的状态仍然为1,但是其他状态为1的文件描述如果没有数据到来,那么此时会将这些文件描述符置为0== +5. 当select函数返回后,可能有一个或者多个文件描述符为1,那么你怎么知道是哪个文件描述符准备好了呢?其实select并不会告诉你说,我哪个文件描述符准备好了,他只会告诉你他的那些bit为位哪些是0,哪些是1。也就是说你需要自己用逻辑去判断你要的那个文件描是否准备好了 + +理解了上面几步的话,下面这些宏就比较好理解了。 + +- FD_ZERO:将指定集合里面所有的描述符全部置为0,==在对文件描述符集合进行设置前,必须对其进行初始化==,如果不清空,由于在系统分配内存空间后,通常并不作清空处理,所以结果是不可知的 +- FD_SET:用于在文件描述符集合中增加一个新的文件描述符,将相应的位置置为1 +- FD_CLR:用来清除集合里面的某个文件描述符 +- FD_ISSET:用来检测指定的某个描述符是否有数据到来。- 那么假如在我们的程序中有5个客户端已经连接上了服务器,这个时候突然有一条数据过来了。select返回了,但是此时你并不知道是哪个客户发过来的消息,因为你每个客户发过来的消息都是一样重要的。所以你没法去只针对一个套接字使用FD_ISSET,你需要做的是用一个循环去检测(FD_ISSET)到底是哪一个客户发过来的消息,因为如果此时你监测一个套接字的话,其他客户的信息你会丢失。这个也是select的一个缺点,你需要去检测所有的套接字,看看这个套接字到底是谁来的数据。 + + + +### 6.2对fd_set的理解 + +理解select模型的关键在于理解fd_set,为说明方便,取fd_set长度为1字节,fd_set中的每一bit可以对应一个文件描述符fd。则1字节长的fd_set最大可以对应8个fd。 + +(1)执行fd_set set; FD_ZERO(&set); 则set用位表示是0000,0000。 + +(2)若fd=5,执行FD_SET(fd,&set);后set变为0001,0000(第5位置为1) + +(3)若再加入fd=2,fd=1,则set变为0001,0011 + +(4)执行select(6,&set,0,0,0)阻塞等待 + +(5)若fd=1,fd=2上都发生可读事件,则select返回,此时set变为0000,0011。注意:没有事件发生的fd=5被清空。 + + + +内容来自[Linux编程之select](https://www.cnblogs.com/skyfsm/p/7079458.html) + +### 6.3select代码演示 + +**select的使用流程** + +1.先调用宏FD_ZERO将指定的fd_set清零, + +2.然后调用宏FD_SET将需要测试的fd加入fd_set, + +3.接着调用函数select监测fd_set中的所有fd, + +4.最后用宏FD_ISSET检查某个fd在函数select调用后,相应位是否仍然为1,然后做相应的逻辑处理。 + +```c +#include +#include +#include +#include +#include + +int main(void) { + fd_set rfds; + struct timeval tv; + int retval; + + /* Watch stdin (fd 0) to see when it has input. */ + + FD_ZERO(&rfds);//将rdfs的fd_set清零, + FD_SET(0, &rfds);//fd = 0 + + /* Wait up to five seconds. */ + + tv.tv_sec = 5;//等待5秒 + tv.tv_usec = 0;//设置等待微秒 + + retval = select(1, &rfds, NULL, NULL, &tv);//fd + 1 + /* Don't rely on the value of tv now! */ + + if (retval == -1) + perror("select()");//失败 + else if (retval) + printf("Data is available now.\n");//成功 + /* FD_ISSET(0, &rfds) will be true. */ + else + printf("No data within five seconds.\n");//超时 + + exit(EXIT_SUCCESS); +} +``` + + + + + +```shell +./a.out +ls +Data is available now. +apricity@Apricity% ls +00.head.h 01.open.c 02.fopen.c 03.fflush.c 04.common.c 05.common.h 06.fork.c 07.testblock.c 08.select.c a.out b.txt +``` + + + +==>为什么会出现 ls命令 + +输入ls,select感知IO成功,程序执行完毕,ls被zsh执行 + +```c +else if (retval) { + char buff[512] = {0}; + printf("Data is available now.\n");//成功 + scanf("%s", buff);//吃掉输入的数据 +} + +``` + + + +## 6.补充:用户态和内核态 + + + + + + + + + + + + + + + +# 4.多进程 + +## 1.什么是进程 + + + +> 什么是程序 + +程序是编译好的可执行的二进制文件。应用是程序的集合。 + + + +> 什么是进程 + +一种说法是进程是程序在内存中的镜像,另一种常见的说法是进程是运行中的程序。 + +进程(process)是一个可执行程序(program)的实例。 + + + +**进程号和父进程号** + +​ 每个进程都有一个进程号(PID),进程号是一个正数,用以唯一标识系统中的某个进程。对 各种系统调用而言,进程号有时可以作为传入参数,有时可以作为返回值。 + +​ 系统调用 getpid()返回调用进程的进程号。 + +```c +#include +pid_t getpid(void); +pid_t getppid(void); +``` + +​ getpid()返回值的数据类型为 pid_t,该类型是由 SUSv3 所规定的整数类型,专用于存储进 程号。 + +​ 除了少数系统进程外,比如 init 进程(进程号为 1),程序与运行该程序进程的进程号之 间没有固定关系。 + +​ 每个进程都有一个创建自己的父进程。使用系统调用 getppid()可以检索到父进程的进程号。 + +​ 实际上,每个进程的父进程号属性反映了系统上所有进程间的树状关系。每个进程的父 进程又有自己的父进程,以此类推,回溯到 1 号进程—init 进程,即所有进程的始祖。 + +​ 如果子进程的父进程终止,则子进程就会变成“孤儿”,init 进程随即将收养该进程,子 进程后续对 getppid()的调用将返回进程号 1。 + + + +**孤儿进程与僵尸进程** + +父进程与子进程的生命周期一般都不相同,父、子进程间互有长短。这就引出了下面两个问题。 + +- 谁会是孤儿(orphan)子进程的父进程?进程 ID 为 1 的众进程之祖—init 会接管孤儿 进程。换言之,某一子进程的父进程终止后,对 getppid()的调用将返回 1。这是判定 某一子进程之“生父”是否“在世”的方法之一(前提是假设该子进程由 init 之外的 进程创建)。 + +- 在父进程执行 wait()之前,其子进程就已经终止,这将会发生什么?此处的要点在于,即使 子进程已经结束,系统仍然允许其父进程在之后的某一时刻去执行 wait(),以确定该子进程 是如何终止的。内核通过将子进程转为僵尸进程(zombie)来处理这种情况。这也意味着将 释放子进程所把持的大部分资源,以便供其他进程重新使用。该进程所唯一保留的是内核进 程表中的一条记录,其中包含了子进程ID、终止状态、资源使用数据等信息。 + + ​ 至于僵尸进程名称的由来,则源于 UNIX 系统对电影情节的效仿—无法通过信号来杀死 僵尸进程,即便(银弹)SIGKILL。这就确保了父进程总是可以执行 wait()方法。 + + ​ 当父进程执行 wait()后,由于不再需要子进程所剩余的最后信息,故而内核将删除僵尸进 程。另一方面,如果父进程未执行 wait()随即退出,那么 init 进程将接管子进程并自动调用 wait(),从而从系统中移除僵尸进程. + + ​ 如果父进程创建了某一子进程,但并未执行 wait(),那么在内核的进程表中将为该子进程永 久保留一条记录。如果存在大量此类僵尸进程,它们势必将填满内核进程表,从而阻碍新进程的 创建。既然无法用信号杀死僵尸进程,那么从系统中将其移除的唯一方法就是杀掉它们的父进程 (或等待其父进程终止),此时 init 进程将接管和等待这些僵尸进程,从而从系统中将它们清理掉。 + + + +## 2.fork()详解 + +>fork()创建一个子进程 + +![截屏2021-01-07 下午6.18.51](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-01-07%20%E4%B8%8B%E5%8D%886.18.51.png) + +​ 系统调用 `fork()`允许一进程(父进程)创建一新进程(子进程)。具体做法是,新 的子进程几近于对父进程的翻版:子进程获得父进程的栈、数据段、堆和执行文 本段的拷贝。可将此视为把父进程一分为二,术语 fork 也由此得名。 + +​ 理解 fork()的诀窍是,要意识到,完成对其调用后将存在两个进程,且每个进程都会从 fork() 的返回处继续执行。 + +​ 这两个进程将执行相同的程序文本段,但却各自拥有不同的栈段、数据段以及堆段拷贝。子进程的栈、数据以及栈段开始时是对父进程内存相应各部分的完全复制。执行 fork() 之后,每个进程均可修改各自的栈数据、以及堆段中的变量,而并不影响另一进程。 + +​ 程序代码则可通过 fork()的返回值来区分父、子进程。在父进程中,fork()将返回新创建 子进程的进程 ID。鉴于父进程可能需要创建,进而追踪多个子进程(通过 wait()或类似方法), 这种安排还是很实用的。而 fork()在子进程中则返回 0。如有必要,子进程可调用 getpid()以获 取自身的进程 ID,调用 getppid()以获取父进程 ID。 + +​ 当无法创建子进程时,fork()将返回-1。失败的原因可能在于,进程数量要么超出了系统 针对此真实用户(real user ID)在进程数量上所施加的限制,要么是触及允许该系统创建的最大进程数这一系统级上限。 + + + +**重点** + +1.pid_t进程id + +2.创建一个新的进程,复制了一份自己,新的进程被叫作子进程,父亲和孩子分别运行在一个完全独立隔离的内存空间,fork时,共用一样的内存空间,只有内存才发生变化时,拷贝才会真实发生(称为写拷贝) + +子进程是一个严格的 + +3.==fork()的子进程返回值为0,父进程返回子进程pid== + +4.子进程和父进程的区别: + +子进程和父进程的进程ID不一样,且唯一 + +子进程的父亲ID和父亲pid一样 + +孩子不会继承父亲的内存锁 + +进程资源使用和CPU使用都会被清空 + +- 子进程不继承信号量 + +- 不会继承计时器 + +- 不会继承异步IO操作 + + 进程的属性 + +返回值: + +父进程中返回子进程的pid,子进程返回0, + +getpid获得自己的id + +getppid获得父进程id + + + + + + + +## 3.fork()代码演示 + +### 3.1fork()理解 + +#include "00.head.h" + +```c +#include +#include +#include +#include +#include +#include +#include +#include +#include +``` + + + +```c +#include "00.head.h" +int main() { + + char name[20] = {0}; + scanf("%s", name); + printf("%s", name); + fork(); + return 0; +} +//结果 +guziqiu +guziqiuguziqiu% +``` + +==>输出2个`name变量` + +原因:fork之后的代码会有两份,之前的代码只执行了一次,标准IO是缓冲IO(没有遇到换行或者缓冲区内容过多或者程序结束,printf的内容会留在输出缓冲区,缓冲区内容被fork,return 0 ;后输出缓冲区内容被释放,输出一个name变量) + +```c +#include "00.head.h" +int main() { + + char name[20] = {0}; + scanf("%s", name); + printf("%s\n", name); + fork(); + return 0; +} +``` + +==>输出1个`name` + +标准IO是缓冲IO,缓冲区被换行符刷新 + +```c +#include "00.head.h" +int main() { + + char name[20] = {0}; + scanf("%s", name); + fork(); + printf("%s\n", name); + + return 0; +} +``` + +==>输出2个`name` + +fork后的内容被复制 + +### 3.2.fork()进阶 + +```c +#include "00.head.h" +int main() { + + pid_t pid; + if ((pid = fork()) < 0) {//必须有括号,否则逻辑会不同,子进程一生下来就在11行的位置 + perror("fork()");//fork出错,fork = -1,大部分是因为内存不够 + exit(1); + } + + if (pid == 0) {//==>思考:一定是父进程先执行吗? + //父进程和子进程空间相互独立,谁先跑都没有关系,由内核调度决定 + //基于内核算法,极大概率先执行父进程 + //原因:父进程正在执行,此时复制父进程,父进程执行完才会执行子进程,所以一般会先执行父进程 + //如果此时cpu强制停用父进程,先执行别的进程,则可能会出现子进程先执行 + printf("Child Process!\n"); + } else { + printf("Parent Process\n");//父子进程有同一份代码,孩子从11行开始 + } + //父进程管理子进程, + return 0; +} +``` + + + + + + + +## 4.wait()详解 + +​ 系统调用 `wait()`等待调用进程的任一子进程终止,同时在参数 status 所指向的缓冲区中返回 该子进程的终止状态。 + +```c +#include +#include +pid_t wait(int *wstatus); +``` + +系统调用 wait()执行如下动作。 + +1. 如果调用进程并无之前未被等待的子进程终止 ,调用将一直阻塞,直至某个子进程终止。 如果调用时已有子进程终止,wait()则立即返回。 + + 2. 如果 status 非空,那么关于子进程如何终止的信息则会通过 status 指向的整型变量返回。 + + 3. 内核将会为父进程下所有子进程的运行总量追加进程 CPU 时间以及资源使用 数据。 + +4. 将终止子进程的 ID 作为 `wait()`的结果返回。 + +​ 出错时,wait()返回-1。可能的错误原因之一是调用进程并无之前未被等待的1子进程,此 时会将 errno 置为 ECHILD。换言之,可使用如下代码中的循环来等待调用进程的所有子进程 退出。 + + + + + +子进程退出状态 + +所有的系统进程都在等待子进程的变化,并且获得状态变化(孩子被终结,杀死,唤醒),对于一个被终结的进程,执行wait,由系统释放孩子所关联的资源 + +孩子死了父进程没有为孩子收尸,孩子变成了僵尸进程,没有占用cpu但是会占用系统资源和pid, + +wait没有被执行会变成僵尸进程 + +如果进程以及死了,wait会立马返回,孩子没死,父进程会一直等着,或者收到一个中断信号 + + + +​ 父进程一旦调用了wait就立即阻塞自己,由wait自动分析是否当前进程的某个子进程已经退出,如果让它找到了这样一个已经变成僵尸的子进程,wait就会收集这个子进程的信息,并把它彻底销毁后返回;如果没有找到这样一个子进程,wait就会一直阻塞在这里,直到有一个出现为止。 +注: + +1. 当父进程忘了用wait()函数等待已终止的子进程时,子进程就会进入一种无父进程的状态,此时子进程就是僵尸进程. +2. wait()要与fork()配套出现,如果在使用fork()之前调用wait(),wait()的返回值则为-1,正常情况下wait()的返回值为子进程的PID. +3. 如果先终止父进程,子进程将继续正常进行,只是它将由init进程(PID 1)继承,当子进程终止时,init进程捕获这个状态. +4. 参数status用来保存被收集进程退出时的一些状态,它是一个指向int类型的指针。但如果我们对这个子进程是如何死掉毫不在意,只想把这个僵尸进程消灭掉,(事实上绝大多数情况下,我们都会这样想),我们就可以设定这个参数为NULL,就像下面这样: + `pid = wait(NULL);` + 如果成功,wait会返回被收集的子进程的进程ID,如果调用进程没有子进程,调用就会失败,此时wait返回-1,同时errno被置为ECHILD。 +   如果参数status的值不是NULL,wait就会把子进程退出时的状态取出并存入其中, 这是一个整数值(int),指出了子进程是正常退出还是被非正常结束的,以及正常结束时的返回值,或被哪一个信号结束的等信息。由于这些信息 被存放在一个整数的不同二进制位中,所以用常规的方法读取会非常麻烦,人们就设计了一套专门的宏(macro)来完成这项工作,下面我们来学习一下其中最常用的两个: + 1,WIFEXITED(status) 这个宏用来指出子进程是否为正常退出的,如果是,它会返回一个非零值。 + (请注意,虽然名字一样,这里的参数status并不同于wait唯一的参数–指向整数的指针status,而是那个指针所指向的整数,切记不要搞混了。) + 2, WEXITSTATUS(status) 当WIFEXITED返回非零值时,我们可以用这个宏来提取子进程的返回值,如果子进程调用exit(5)退出,WEXITSTATUS(status) 就会返回5;如果子进程调用exit(7),WEXITSTATUS(status)就会返回7。请注意,如果进程不是正常退出的,也就是说, WIFEXITED返回0,这个值就毫无意义。 + + + + + +## 5.wait()代码演示 + +### 产生僵尸进程 + +`fork.c` + +```c +#include "00.head.h" +int main() { + + pid_t pid; + if ((pid = fork()) < 0) { + perror("fork()"); + exit(1); + } + if (pid == 0) { + printf("Child Process!\n"); + } else { + printf("Parent Process\n"); + while(1) { + printf("--sleep(5)"); + sleep(5); + printf("--"); + } + } + + return 0; +} +``` + + + +```shell + +apricity@Apricity 00.课程代码 % ./a.out +Parent Process +Child Process! +^Z +[1] + 16838 suspended ./a.out +apricity@Apricity 00.课程代码 % bg +[1] + 16838 continued ./a.out +apricity@Apricity 00.课程代码 % ps + PID TTY TIME CMD +16342 pts/0 00:00:03 zsh +16838 pts/0 00:00:00 a.out +16839 pts/0 00:00:00 a.out #僵尸进程 +16842 pts/0 00:00:00 ps +apricity@Apricity 00.课程代码 %ps -aux | grep 16839 +apricity 16839 0.0 0.0 0 0 pts/0 Z 11:12 0:00 [a.out] +apricity 16852 0.0 0.0 14428 1044 pts/0 S+ 11:15 0:00 grep --color=auto --exclude-dir=.bzr --exclude-dir=CVS --exclude-dir=.git --exclude-dir=.hg --exclude-dir=.svn --exclude-dir=.idea --exclude-dir=.tox 16839 +apricity@Apricity 00.课程代码 % ps -aux | grep Z +USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND +apricity 16839 0.0 0.0 0 0 pts/0 Z 11:12 0:00 [a.out] +apricity 16858 0.0 0.0 14428 996 pts/0 S+ 11:16 0:00 grep --color=auto --exclude-dir=.bzr --exclude-dir=CVS --exclude-dir=.git --exclude-dir=.hg --exclude-dir=.svn --exclude-dir=.idea --exclude-dir=.tox Z +#Z的意思就是僵尸进程 +``` + + + +`fork.wait.c` + +```c +#include "00.head.h" +int main() { + + pid_t pid; + int status; + if ((pid = fork()) < 0) { + perror("fork()"); + exit(1); + } + if (pid == 0) { + sleep(5); + printf("Child Process!\n"); + return 1; + } else { + printf("Parent Process\n"); + wait(&status); + printf("--wait status = %d \n", status); + } + + return 0; +} + +``` + + + +```shell +./a.out +Parent Process +Child Process! +--wait status = 256 +``` + + + +创建10个子进程,每个子进程打印自己是子进程 + +```c +#include "00.head.h" +int main() { + + pid_t pid; + int status; + int i; + for (i = 1; i <= 9; i++) { + if ((pid = fork()) < 0) { + perror("fork()"); + exit(1); + } + } + if (pid == 0) { + printf(" I am %d Child Process!\n", i); + sleep(500); + } + return 0; +} +``` + + + +```shell +ps -ef | grep "./a.out" | grep -v "grep" | wc -l +1024 +#一共产生1024个进程 +#2^10 = 1024,会以子数倍增长 +``` + + + +```c +#include "00.head.h" +int main() { + + pid_t pid; + int status; + int i; + for (i = 1; i <= 10; i++) { + if ((pid = fork()) < 0) { + perror("fork()"); + exit(1); + } + if (pid == 0) break;; + } + if (pid == 0) { + printf(" I am %d Child Process!\n", i); + + sleep(500); + + } + return 0; +} +``` + +```shell +./a.out + I am 1 Child Process! + I am 3 Child Process! + I am 2 Child Process! + I am 4 Child Process! + I am 9 Child Process! + I am 10 Child Process! + I am 5 Child Process! + I am 6 Child Process! + I am 7 Child Process! + I am 8 Child Process! +ps -aux | grep "a.out" | grep -v "grep" | wc -l +10 +``` + + + +## 6.exec()家族详解 + +​ 系统调用 execve()可以将新程序加载到某一进程的内存空间。在这一操作过程中,将丢弃 旧有程序,而进程的栈、数据以及堆段会被新程序的相应部件所替换。 + +​ 基于系统调用 execve(),还提供了一系列冠以 exec 来命名的上层库函数,虽然接口方式 各异,但功能相同。通常将调用这些函数加载一个新程序的过程称作 exec 操作,或是简单地 以 exec()来表示。 + +```c +#include +extern char **environ; +int execl(const char *path, const char *arg, ... + /* (char *) NULL */); +int execlp(const char *file, const char *arg, ... + /* (char *) NULL */); +int execle(const char *path, const char *arg, ... + /*, (char *) NULL, char * const envp[] */); +int execv(const char *path, char *const argv[]); +int execvp(const char *file, char *const argv[]); +int execvpe(const char *file, char *const argv[], + char *const envp[]); +``` + +​ + +​ 参数 pathname 包含准备载入当前进程空间的新程序的路径名,既可以是绝对路径(冠 之以/),也可以是相对于调用进程当前工作目录(current working directory)的相对路径。 + +​ 参数 argv 则指定了传递给新进程的命令行参数。该数组对应于 C 语言 main()函数的第 2 个参数(argv),且格式也与之相同:是由字符串指针所组成的列表,以 NULL 结束。argv[0]的 值则对应于命令名。通常情况下,该值与 pathname 中的 basename(路径名的最后部分)相同。 + +​ 最后一个参数 envp 指定了新程序的环境列表。参数 envp 对应于新程序的 environ 数组:也是由字符串指针组成的列表,以 NULL 结束,所指向的字符串格式为 name=value。 + + + + exec函数族的作用是根据指定的文件名找到可执行文件,并用它来取代调用进程的内容,换句话说,就是在调用进程内部执行一个可执行文件**。**这里的可执行文件既可以是二进制文件,也可以是任何Linux下可执行的脚本文件。 + +  与一般情况不同,exec函数族的函数执行成功后不会返回,因为调用进程的实体,包括代码段,数据段和堆栈等都已经被新的内容取代,只留下进程ID等一些表面上的信息仍保持原样。只有调用失败了,它们才会返回一个-1,从原程序的调用点接着往下执行 + + + + + +exec族的任一函数都不创建一个新的进程,而是在调用进程里面去执行新的程序。所以进程id不变,还是调用exec函数前的进程id,但是用户空间的代码和数据都更新了,变为新程序的代码和数据了。 + +​ extern char **environ; //全局环境变量,导入到本文件即可直接使用 + +1. int execl(const char *path, const char *arg, ...); + +​ 功能:通过路径+文件名来加载一个进程;path文件路径;arg文件名称;...可变参数,至少一个NULL + +​ 附:l即list + +​ 返回值:成功的情况下是没有返回的,失败时返回-1 。 + +​ 举例说明: + +​ execl("/bin/ls", "ls", "-a", "-l", NULL); //path绝对路径,如/bin/ls;文件名称ls;后面三个可变参数,最后必须以NULL结束 + +2. int execlp(const char *file, const char *arg, ...); + +​ 功能:借助PATH环境变量加载一个进程,file要加载的程序的名称 + +​ 附:l即list;p即path + +​ 该函数需要配合PATH环境变量来使用,当PATH中所有目录搜索后没有参数file,则出错返回。 + +​ 该函数通常用来调用系统程序。如:ls、cp、cat等命令。 + +​ 返回值:成功的情况下是没有返回的,失败时返回-1 。 + +​ 举例说明: + +​ execlp("ls", "ls", "-a", "-l", NULL); //第一个ls是指查看PATH环境变量里的ls;第二个ls是名称文件;后面是可变参数,NULL结束 + +3. int execle(const char *path, const char *arg, ..., char * const envp[]); + +​ 功能:加载指定路径的程序,并为新程序复制最后一个环境变量 + + 附:l即list;e即environment + +​ 举例说明: + +​ char* envp[] = {NULL}; + +​ execlp("ls", "ls", "-a", "-l", NULL, envp); + +4. int execv(const char *path, char *const argv[]); + +​ 功能:加载指定路径的程序 + +​ 附:v即vector,命令行参数列表 + +​ 举例说明: + +​ char* argv[] = {"ls", "-a", "-l", NULL}; + +​ execl("/bin/ls",argv); + +5. int execvp(const char *file, char *const argv[]); + +​ 功能:加载path环境变量里的名称为file的程序 + +​ 附:v即命令行参数列表,p即path + +​ int main(int argc, char *argv[]) { + +​ pid_t pid = fork(); + +​ if (pid == 0) { //子进程里加载ls程序 + +​ char* argvv[] = {"ls", "-a", "-l", NULL}; + +​ execvp("ls", argvv); + +​ perror("execlp"); exit(1); //只有execl函数执行失败的情况下才有机会执行这两句代码,执行的成功话就有去无回了。 + +​ } else if (pid > 0) { + +​ sleep(1); printf("parent\n"); + +​ } + +​ return 0; + +​ } + +6. int execve(const char *filename, char *const argv[], char *const envp[]); + +​ 功能:加载指定的程序;filename必须是一个可执行程序或者一个以#! interpreter [optional-arg] 开始的脚本。 + +​ 上面的五个exec函数是库函数,这个是系统函数;上面的五个exec函数最终都是调用这个函数实现的。 + + + +总结:exec族函数的规律 + +​ exec函数一旦调用成功就有去无回了,去执行新的程序去了。只有失败时才有返回,返回值为-1。所以我们直接在exec函数调用后直接调用perror()和exit(),不需要if判断,因为失败的情况才会执行。 + +​ 函数名的意义的理解: + +​ l (list) 命令行参数列表 + +​ p (path) 环境变量,环境变量搜素文件名称file + +​ v (vector) 命令行参数数组 + +​ e (environment) 环境变量数组,给新加载程序设置指定的环境变量 + +​ 函数的相似性: + +​ execlp——>execvp + +​ | + +​ execl ——>execv + +​ | + +​ execle——>execve + +​ 从左往右,可变参数转为以NULL结尾的指针数组;从左往右, 从上往下,最后归根结底都是调用execve函数实现的。 + + + + + + + + + + + + + + + +替换当前进程映像用一个新的进程映像, + +## 7.exec()代码演示 + + + +```c +#include "00.head.h" + +int main() { + pid_t pid; + if ((pid = fork()) < 0) { + perror("fork"); + exit(-1); + } + + if (pid == 0) { + printf("start\n"); + execlp("vim", "vim", "12.exec.c", NULL);//子进程,在子进程中执行vim,名字是vim,名字是12.exec.c + sleep(10000000);//子进程中不存在 + printf("END!\n");//子进程中不存在 + } else { + wait(NULL); + printf("After child terminated!\n"); + sleep(4); + } + + return 0; +} +``` + + + +```c +//12.exec.c +#include "00.head.h" + +int main() { + pid_t pid; + if ((pid = fork()) < 0) { + perror("fork"); + exit(-1); + } + + if (pid == 0) { + printf("start\n"); + execl("./a.out", "haha", "12.exec.c", NULL); + sleep(10000000); + printf("END!\n"); + } else { + wait(NULL); + printf("After child terminated!\n"); + sleep(4); + } + + return 0; +} +//13.test_exec.c +#include + +int main(int argc, char **argv) { + printf("arg0 = %s\n", argv[0]); + return 0; +} + +``` + + + +```shell +gcc 13.test_exec.c +./a.out +arg0 = ./a.out +gcc 12.exec.c -o exec + ./exec +start +arg0 = haha#习惯上将此处设置为文件名 +After child terminated! +``` + + + +## 8.高级进程管理 + +进程调度 + +进程调度是一个内核子系统 + +进程调度的主要任务是决定哪一个“就绪”状态的进程来执行 + +就绪进程就是 非阻塞进程 + +阻塞进程就是正在睡眠的进程,需要内核唤醒的进程; + + + +![截屏2021-01-21 下午2.43.53](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-01-21%20%E4%B8%8B%E5%8D%882.43.53.png) + + + +# 5.进程间通信上 + +## 5.1进程间通信 + +Linux 系统上运行有多个进程,其中许多都是独立运行。然而,有些进程必须相互合作以达成预期目的,因此彼此间需要通信和同步机制。 + +​ 读写磁盘文件中的信息是进程间通信的方法之一。可是,对许多程序来说,这种方法既 慢又缺乏灵活性。因此,像所有现代 UNIX 实现那样,Linux 也提供了丰富的进程间通信(IPC) 机制,如下所示。 + +- 信号(signal),用来表示事件的发生。 +- 管道(亦即 shell 用户所熟悉的“|”操作符)和 FIFO,用于在进程间传递数据。 +- 套接字,供同一台主机或是联网的不同主机上所运行的进程之间传递数据。 +- 文件锁定,为防止其他进程读取或更新文件内容,允许某进程对文件的部分区域加以 锁定。 +- 消息队列,用于在进程间交换消息(数据包)。 +- 信号量(semaphore),用来同步进程动作。 +- 共享内存,允许两个及两个以上进程共享一块内存。当某进程改变了共享内存的内容 时,其他所有进程会立即了解到这一变化。 + +​ + +## 5.2System V 共享内存 + +​ 共享内存允许两个或多个进程共享物理内存的同一块区 域(通常被称为段)。由于一个共享内存段会成为一个进程用户空间内存的一部分,因此这种 IPC 机制无需内核介入。所有需要做的就是让一个进程将数据复制进共享内存中,并且这部分 数据会对其他所有共享同一个段的进程可用。与管道或消息队列要求发送进程将数据从用户 空间的缓冲区复制进内核内存和接收进程将数据从内核内存复制进用户空间的缓冲区的做法 相比,这种 IPC 技术的速度更快。(每个进程也存在通过系统调用来执行复制操作的开销。) + +​ 另一方面,共享内存这种 IPC 机制不由内核控制意味着通常需要通过某些同步方法使得 进程不会出现同时访问共享内存的情况(如两个进程同时执行更新操作或者一个进程在从共 享内存中获取数据的同时另一个进程正在更新这些数据)。System V 信号量天生就是用来完成 这种同步的一种方法。当然,还可以使用其他方法,如 POSIX 信号量和文件锁. + + + +### 5.2.1共享内存的使用 + +为使用一个共享内存段通常需要执行下面的步骤。 + +- 调用 shmget()创建一个新共享内存段或取得一个既有共享内存段的标识符(即由其他进程 创建的共享内存段)。这个调用将返回后续调用中需要用到的共享内存标识符。 +- 使用 shmat()来附上共享内存段,即使该段成为调用进程的虚拟内存的一部分。 +- 此刻在程序中可以像对待其他可用内存那样对待这个共享内存段。为引用这块共享内 存,程序需要使用由 shmat()调用返回的 addr 值,它是一个指向进程的虚拟地址空间 中该共享内存段的起点的指针。 +- 调用 shmdt()来分离共享内存段。在这个调用之后,进程就无法再引用这块共享内存 了。这一步是可选的,并且在进程终止时会自动完成这一步。 +- 调用 shmctl()来删除共享内存段。只有当当前所有附加内存段的进程都与之分离之后 内存段才会被销毁。只有一个进程需要执行这一步。 + +### 5.2.2创建或打开一个共享内存段shmget() + +shmget()系统调用创建一个新共享内存段或获取一个既有段的标识符。新创建的内存段中 的内容会被初始化为 0。 + +```cpp +NAME + shmget - allocates a System V shared memory segment + +SYNOPSIS + #include + #include + + int shmget(key_t key, size_t size, int shmflg); + +``` + + + +​ 当使用 shmget()创建一个新共享内存段时,size 则是一个正整数,它表示需分配的段的字 节数。内核是以系统分页大小的整数倍来分配共享内存的,因此实际上 size 会被提升到最近 的系统分页大小的整数倍。如果使用 shmget()来获取一个既有段的标识符,那么 size 对段不会 产生任何效果,但它必须要小于或等于段的大小。 + +​ shmflg 参数执行的任务与其在其他 IPC get 调用中执行的任务一样,即指定施加于新共享 内存段上的权限或需检查的既有内存段的权限。此外,在 shmflg 中还可以对下列标 记中的零个或多个取 OR 来控制 shmget()的操作。 + +​ IPC_CREAT + +如果不存在与指定的 key 对应的段,那么就创建一个新段。 + +​ IPC_EXCL + +如果同时指定了IPC_CREAT 并且与指定的key 对应的段已经存在,那么返回EEXIST 错误。 SHM_HUGETLB(自 Linux 2.6 起) + +特权(CAP_IPC_LOCK)进程能够使用这个标记创建一个使用巨页(huge page)的共享内存 段。巨页是很多现代硬件架构提供的一项特性用来管理使用超大分页尺寸的内存。(如 x86-32 允 许使用 4MB 的分页大小来替代 4KB 的分页大小。)在那些拥有大量内存的系统上并且应用程序需 要使用大量内存块时,使用巨页可以降低硬件内存管理单元的超前转换缓冲器(translation look-aside buffer,TLB)中的条目数量。这之所以会带来益处是因为 TLB 中的条目通常是一种稀 缺资源。更多信息可参考内核源文件 Documentation/vm/ hugetlbpage.txt。 + +SHM_NORESERVE(自 Linux 2.6.15 起) + + 这个标记在 shmget()中所起的作用与 MAP_NORESERVE 标记在 mmap()中所起的作用一 样。 shmget()在成功时返回新或既有共享内存段的标识符。 + +### 5.2.3 使用共享内存shmat() + +shmat()系统调用将 shmid 标识的共享内存段附加到调用进程的虚拟地址空间中。 + +```shell +NAME + shmat, shmdt - System V shared memory operations + +SYNOPSIS + #include + #include + + void *shmat(int shmid, const void *shmaddr, int shmflg); + + int shmdt(const void *shmaddr); + + +``` + +```cpp + +RETURN VALUE + On success, a valid shared memory identifier is returned. On error, -1 is returned, and errno is set to indicate the error. +``` + + + +shmaddr 参数和 shmflg 位掩码参数中 SHM_RND 位的设置控制着段是如何被附加上去的。 + +- 如果 shmaddr 是 NULL,那么段会被附加到内核所选择的一个合适的地址处。这是附 加一个段的优选方法。 +- 如果 shmaddr 不为 NULL 并且没有设置 SHM_RND,那么段会被附加到由 shmaddr 指 定的地址处,它必须是系统分页大小的一个倍数(否则会发生 EINVAL 错误)。 +- 如果 shmaddr 不为 NULL 并且设置了 SHM_RND,那么段会被映射到的地址为在 shmaddr 中提供的地址被舍入到最近的常量 SHMLBA(shared memory low boundary address)的倍数。这个常量等于系统分页大小的某个倍数。将一个段附加到值为 SHMLBA 的倍数的地址处在一些架构上是有必要的,因为这样才能够提升 CPU 的快 速缓冲性能和防止出现同一个段的不同附加操作在 CPU 快速缓冲中存在不一致的视 图的情况 + +​ shmat()的函数结果是返回附加共享内存段的地址。开发人员可以像对待普通的 C 指针那 样对待这个值,段与进程的虚拟内存的其他部分看起来毫无差异。通常会将 shmat()的返回值 赋给一个指向某个由程序员定义的结构的指针以便在该段上设定该结构。 + +​ 要附加一个共享内存段以供只读访问,那么就需要在 shmflg 中指定 SHM_RDONLY 标记。 试图更新只读段中的内容会导致段错误(SIGSEGV 信号)的发生。如果没有指定 SHM_ RDONLY,那么就既可以读取内存又可以修改内存。 + +​ 一个进程要附加一个共享内存段就需要在该段上具备读和写权限,除非指定了 SHM_ RDONLY 标记,那样的话就只需要具备读权限即可。 + +​ 最后一个可以在 shmflg 中指定的值是 SHM_REMAP。在指定了这个标记之后 shmaddr 的值 必须为非 NULL。这个标记要求 shmat()调用替换起点在 shmaddr 处长度为共享内存段的长度的 任何既有共享内存段或内存映射。一般来讲,如果试图将一个共享内存段附加到一个已经在用 的地址范围时将会导致 EINVAL 错误的发生。SHM_REMAP 是一个非标准的 Linux 扩展。 + +![截屏2021-01-22 上午10.43.15](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-01-22%20%E4%B8%8A%E5%8D%8810.43.15.png) + +​ + +​ 当一个进程不再需要访问一个共享内存段时就可以调用 shmdt()来讲该段分离出其虚拟地 址空间了。shmaddr 参数标识出了待分离的段,它应该是由之前的 shmat()调用返回的一个值。 + +```c +int shmdt(const void *shmaddr); +``` + +​ 分离一个共享内存段与删除它是不同的。删除是通过 shmctl() IPC_ RMID 操作来完成的。 + +​ 通过 fork()创建的子进程会继承其父进程附加的共享内存段。因此,共享内存为父进程和 子进程之间的通信提供了一种简单的 IPC 方法。 + +​ 在一个 exec()中,所有附加的共享内存段都会被分离。在进程终止之后共享内存段也会自 动被分离 + + + +### 5.2.4ipcs 和 ipcrm 命令 + +​ ipcs 和 ipcrm 命令是 System V IPC 领域中类似于 ls 和 rm 文件命令的命令。使用 ipcs 能够 获取系统上 IPC 对象的信息。在默认情况下,ipcs 会显示出所有对象,如下面的例子所示。 + +![截屏2021-01-22 上午10.52.09](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-01-22%20%E4%B8%8A%E5%8D%8810.52.09.png) + +​ 在 Linux 上,ipcs(1)只显示出拥有读权限的 IPC 对象的信息,而不管是否拥有这些对象。 在一些 UNIX 实现上,ipcs 的行为与它在 Linux 上的行为一样,但在其他实现上,ipcs 会显示 出所有对象,不管当前用户是否拥有这些对象上的读权限。 + +​ 在默认情况下,ipcs 会显示出每个对象的 key、标识符、所有者以及权限(用一个八进制 数字表示),后面跟着对象所特有的信息。 + +- 对于共享内存,ipcs 会显示出共享内存区域的大小、当前将共享内存区域附加到自己 的虚拟地址空间的进程数以及状态标记。状态标记标识出了区域是否被锁进了 RAM 以防止交换以及在所有进程都与该区域分离之后是否已经将其标记为待销毁了。 +- 对于信号量,ipcs 会显示出信号集的大小。 +- 对于消息队列,ipcs 会显示出队列中数据占据的字节总数以及消息数量。 + + ipcs(1)手册对各种能够显示 IPC 对象的其他信息的选项进行了说明。 ipcrm 命令删除一个 IPC 对象。这个命令的常规形式为下面两种形式中的一种 + +```shell +ipcrm -X key +ipcrm -x id +``` + +​ 在上面给出的命令中既可以将一个 IPC 对象的 key 指定为参数 key,也可以将一个 IPC 对象 的标识符指定为参数 id 并且使用小写的 x 替换其大写形式或使用小写的 q(用于消息队列)或 s (用于信号量)或 m(用于共享内存)。因此使用下面的命令可以删除标识符为 65538 的信号量集。 + +```shell +ipcrm -s 65538 #删除标识符为 65538 的信号量集 +ipcrm -m 65538 #删除标识符为 65538 的共享内存 +``` + + + + + +## 5.3互斥量(mutexe)和条件变量(condition variable) + +​ 互斥量可以帮助线程同步对共享资源的使用,以防如下情况发生:线程某甲试图访 问一共享变量时,线程某乙正在对其进行修改。条件变量则是在此之外的拾遗补缺,允许线 程相互通知共享变量(或其他共享资源)的状态发生了变化。 + +​ 互斥量既可以像静态变量那样分配,也可以在运行时动态创建。 + +​ 互斥量是属于 pthread_mutex_t 类型的变量。在使用之前必须对其初始化。对于静态分配 的互斥量而言,可将 PTHREAD_MUTEX_INITIALIZER 赋给互斥量。 + +​ 初始化之后,互斥量处于未锁定状态。函数 pthread_mutex_lock()可以锁定某一互斥量,而 函数 pthread_mutex_unlock()则可以将一个互斥量解锁。 + +```cpp +NAME + pthread_mutex_init, pthread_mutex_lock, pthread_mutex_trylock, pthread_mutex_unlock, pthread_mutex_destroy - operations on mutexes + +SYNOPSIS + #include + + pthread_mutex_t fastmutex = PTHREAD_MUTEX_INITIALIZER; + + pthread_mutex_t recmutex = PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP; + + pthread_mutex_t errchkmutex = PTHREAD_ERRORCHECK_MUTEX_INITIALIZER_NP; + + int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *mutexattr); + + int pthread_mutex_lock(pthread_mutex_t *mutex); + + int pthread_mutex_trylock(pthread_mutex_t *mutex); + + int pthread_mutex_unlock(pthread_mutex_t *mutex); + + int pthread_mutex_destroy(pthread_mutex_t *mutex); +``` + + + +```cpp +RETURN VALUE + pthread_mutex_init always returns 0. The other mutex functions return 0 on success and a non-zero error code on error. +``` + + + +​ 要锁定互斥量,在调用 pthread_mutex_lock()时需要指定互斥量。如果互斥量当前处于未锁定 状态,该调用将锁定互斥量并立即返回。如果其他线程已经锁定了这一互斥量,那么 pthread_ mutex_lock()调用会一直堵塞,直至该互斥量被解锁,到那时,调用将锁定互斥量并返回。 + +​ 如果发起 pthread_mutex_lock()调用的线程自身之前已然将目标互斥量锁定,对于互斥量 的默认类型而言,可能会产生两种后果—视具体实现而定:线程陷入死锁(deadlock),因 试图锁定已为自己所持有的互斥量而遭到阻塞;或者调用失败,返回 EDEADLK 错误。在 Linux 上,默认情况下线程会发生死锁。 + +​ 函数 pthread_mutex_unlock()将解锁之前已遭调用线程锁定的互斥量。以下行为均属错误: 对处于未锁定状态的互斥量进行解锁,或者解锁由其他线程锁定的互斥量。 + + + +```c + int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *mutexattr); +``` + + + +​ 参数 mutex 指定函数执行初始化操作的目标互斥量。参数 attr 是指向pthread_mutexattr_t 类 型对象的指针,该对象在函数调用之前已经过了初始化处理,用于定义互斥量的属性。若将 attr 参数置为 NULL,则该互斥量的各种属性会取默认值。 + +​ SUSv3 规定,初始化一个已初始化的互斥量将导致未定义的行为,应当避免这一行为。 + +​ 在如下情况下,必须使用函数 pthread_mutex_init(),而非静态初始化互斥量。 + +- 动态分配于堆中的互斥量。例如,动态创建针对某一结构的链表,表中每个结构都包 含一个 pthread_mutex_t 类型的字段来存放互斥量,借以保护对该结构的访问。 + +- 互斥量是在栈中分配的自动变量。 + +- 初始化经由静态分配,且不使用默认属性的互斥量。 + + 当不再需要经由自动或动态分配的互斥量时,应使用 pthread_mutex_destroy()将其销毁。 + +​ 只有当互斥量处于未锁定状态,且后续也无任何线程企图锁定它时,将其销毁才是安全 的。若互斥量驻留于动态分配的一片内存区域中,应在释放(free)此内存区域前将其销毁。 对于自动分配的互斥量,也应在宿主函数返回前将其销毁。 + +​ 经由 pthread_mutex_destroy()销毁的互斥量,可调用 pthread_mutex_init()对其重新初始化。 + + + + + +```cpp +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#define INS 5 + +struct Num { + int now; + int sum; + pthread_mutex_t mutex; + pthread_cond_t cond; +}; +struct Num *share_memory; +void do_add(int x) { + while (1) { + pthread_mutex_lock(&share_memory->mutex); + pthread_cond_wait(&share_memory->cond, &share_memory->mutex); + int flag = 0; + for (int i = 0; i < 100; i++) { + if (share_memory->now > 1000) { + pthread_mutex_unlock(&share_memory->mutex); + pthread_cond_signal(&share_memory->cond); + exit(0); + } + share_memory->sum += share_memory->now; + share_memory->now++; + printf("<%d> now = %d, sum = %d\n", x, share_memory->now, share_memory->sum); + fflush(stdout); + //pthread_mutex_unlock(&share_memory->mutex); + } + pthread_mutex_unlock(&share_memory->mutex); + pthread_cond_signal(&share_memory->cond); + } + exit(0); +} + +int main() { + pid_t pid; + int x = 0, shmid; + key_t key = ftok(".", 2021); + if ((shmid = shmget(key, sizeof(struct Num), IPC_CREAT | IPC_EXCL | 0666)) < 0) { + perror("sshmget"); + exit(-1); + } + share_memory = (struct Num*)shmat(shmid, NULL, 0); + if (share_memory == (struct Num*)-1) { + perror("share_memory"); + exit(-1); + } + share_memory->now = 0; + share_memory->sum = 0; + + + pthread_mutexattr_t attr; + pthread_mutexattr_init(&attr); + pthread_mutexattr_setpshared(&attr, PTHREAD_PROCESS_SHARED); + pthread_mutex_init(&share_memory->mutex, &attr); + + pthread_condattr_t condattr; + pthread_condattr_init(&condattr); + pthread_condattr_setpshared(&condattr, 1); + pthread_cond_init(&share_memory->cond, &condattr); + + + //printf("before fork()\n"); + for (int i = 1; i <= INS; i++) { + if ((pid = fork()) < 0) { + perror("fork"); + } + x = i; + if (pid == 0) break; + } + + if (pid == 0) { + do_add(x); + } else { + sleep(1); + pthread_cond_signal(&share_memory->cond); + for (int i = 1; i <= INS; i++) { + wait(NULL); + } + } + printf("end ==> share_memory->sum = %d \n", share_memory->sum); + shmdt(share_memory); + shmctl(shmid, IPC_RMID,NULL); + + return 0; +} +``` + + + +## 5.4进程调度 + + + +![截屏2021-01-21 下午2.47.16](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-01-21%20%E4%B8%8B%E5%8D%882.47.16.png) + + + +![截屏2021-01-21 下午2.49.36](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-01-21%20%E4%B8%8B%E5%8D%882.49.36.png) + +![截屏2021-01-21 下午2.52.55](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-01-21%20%E4%B8%8B%E5%8D%882.52.55.png) + +![截屏2021-01-21 下午3.02.41](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-01-21%20%E4%B8%8B%E5%8D%883.02.41.png) + +![截屏2021-01-21 下午3.06.29](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-01-21%20%E4%B8%8B%E5%8D%883.06.29.png) + +![截屏2021-01-21 下午3.08.34](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-01-21%20%E4%B8%8B%E5%8D%883.08.34.png) + +![截屏2021-01-21 下午3.09.54](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-01-21%20%E4%B8%8B%E5%8D%883.09.54.png) + + + + + + + + + + + +# 6.进程间通信下 + +## 互斥锁 + +shmctl + + + +ftok + + + +## 简易聊天系统 + + + +`18.ipc.chat.server.c` + +```c +#include "00.head.h" +#include "17.ipc.chat.h" + +struct Msg *shar_memory = NULL; +//服务端 +int main() { + int shmid; + key_t key = ftok(".", 202101); + if ((shmid = shmget(key, sizeof(struct Msg), IPC_CREAT | 0666)) < 0) { + perror("shmget"); + exit(1); + } + + if ((shar_memory = (struct Msg *)shmat(shmid, NULL, 0)) == (struct Msg *)-1) { + perror("shmat"); + exit(-1); + } + //初始化 + memset(shar_memory, 0, sizeof(struct Msg)); + pthread_mutexattr_t m_attr; + pthread_mutexattr_init(&m_attr); + pthread_mutexattr_setpshared(&m_attr, PTHREAD_PROCESS_SHARED); + pthread_mutex_init(&shar_memory->mutex, &m_attr); + //初始化一个互斥锁 //互斥锁标识 //互斥锁属性 + + pthread_condattr_t c_attr; + pthread_condattr_init(&c_attr); + pthread_condattr_setpshared(&c_attr, PTHREAD_PROCESS_SHARED); + pthread_cond_init(&shar_memory->cond, &c_attr); + //初始化条件变量 + + while (1) { + // 对互斥锁上锁,若互斥锁已经上锁,则调用者一直阻塞,// 直到互斥锁解锁后再上锁。 + pthread_mutex_lock(&shar_memory->mutex); + printf("Server got the Mutex!\n"); + //阻塞等待 + pthread_cond_wait(&shar_memory->cond, &shar_memory->mutex); + printf("Server got the cond signal!\n"); + printf("<%s> : %s.\n", shar_memory->name, shar_memory->msg); + memset(shar_memory->msg, 0, sizeof(shar_memory->msg)); + pthread_mutex_unlock(&shar_memory->mutex);// 对指定的互斥锁解锁。 + + } + + return 0; +} +``` + + + +`19.ipc.chat.client.c` + +```c +#include "00.head.h" +#include "17.ipc.chat.h" + +struct Msg *shar_memory = NULL; + +int main(int argc, char **argv) { + + int opt, shmid;//.,共享内存id + char name[20] = {0}; + while ((opt = getopt(argc, argv, "n:")) != -1) { + switch (opt) { + case 'n': + strcpy(name, optarg); + break; + default: + fprintf(stderr, "Usage : %s -n name\n", argv[0]); + exit(1); + } + } + + key_t key = ftok(".", 202101); + //创建共享内存 + if ((shmid = shmget(key, sizeof(struct Msg), IPC_CREAT | 0666)) < 0) { + perror("shmget"); + exit(1); + } + // 进程将共享内存连接到自身的地址空间 + if ((shar_memory = (struct Msg *)shmat(shmid, NULL, 0)) == (struct Msg *)-1) { + perror("shmat"); + exit(1); + } + + while (1) { + char msg[1024] = {0}; + scanf("%[^\n]s", msg); + getchar();//吃掉缓冲区的回车键 + if (!strlen(msg)) continue;//字符串为空 + while (1) { + if (!strlen(shar_memory->msg)) {//当写入数据前,加锁 + pthread_mutex_lock(&shar_memory->mutex); + } + } + //pthread_mutex_lock(&shar_memory->mutex); + printf("Sending : %s...\n", msg); + strcpy(shar_memory->msg, msg);//向共享内存中写入数据 + strcpy(shar_memory->name, name); + pthread_cond_signal(&shar_memory->cond);//至少唤醒一个等待该条件的线程 + pthread_mutex_unlock(&shar_memory->mutex);// //写入数据后,解锁 + printf("Client signaled the cond\n"); + } + + + + return 0; +} + +``` + +`17.ipc.chat.h` + +```c +struct Msg{ + char name[20]; + char msg[1024]; + pthread_mutex_t mutex; + pthread_cond_t cond; +}; +``` + + + +`00.head.h` + +```c +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +``` + + + + + +## 线程 + +线程是进程的一个分支 + +pthread process thread + +一个进程可以有多个线程,本质上是一种轻量级进程 + +共享内存,通信简单,调度成本低, + +进程与进程之间调度成本高: + +有时间局部性,运行一段时间,就将进程调度出去,换一个新的程序,缓冲也会被干掉 + +线程本质上的共享内存,一个进程的多个线程在调度的时候,不需要做切换,直接用就可以,原先线程建立起的缓冲信息可以直接用 + +pthread_create + + + +argument参数 + +pthread_exit(3)线程自杀 + +线程创建 + +```c +#include "00.head.h" + +struct MyArg { + char name[20]; + int age; +}; + +void *print(void *arg) { + struct MyArg copyMyarg; + copyMyarg = *(struct MyArg *)arg;//防止在执行过程中会被其他线程改变,所以先拷贝下来 + struct MyArg *in; + in = (struct MyArg *)arg; + + printf("In Thread\n"); + printf("%s is %d years old!\n", in->name, in->age); + printf("%s is %d years old!\n", copyMyarg.name, copyMyarg.age); +} + +int main() { + pthread_t thread; + char name[20]; + int age = 18; + struct MyArg arg; + strcpy(arg.name, "古子秋"); + arg.age = age; + pthread_create(&thread, NULL, print, &arg); + usleep(1); + return 0; +} +``` + + + +## 线程池 + +线程是为了解决一个确定性的工作 + +用理发店举例 + +刚开始创业开理发店 + +只有我一个人会理发,如果来了一个人,就可以直接理发,如果来了多个人,除了第一个其他人就只能处于等待状态,就相当于早期的单线程或者单进程处理 + +现在我学会了一种神奇的技术,叫克隆(fork()),如果我正在理发,又有新客户新来了,那么我克隆一个自己,让他去服务,这就是多进程 + +但是我发现,克隆自己的代价太大了,系统只能同意我们打开1024个进程,而且拷贝进程会占内存,国家又禁止克隆技术 + +现在突然发现旁边的理发学院有的学生想做兼职,如果来了很多客人,我就在微信群里面喊多少人过来做兼职,做完了就让他们走。(多线程) + + 但是创建进程或者线程比较麻烦,客人需要等待,又要创建又要销毁, + +现在我发现店里面人很多。基本上天天都有人,然后我就在人才市场找了几个固定员工,签了合同,如果有人来就去照顾客人,没有人就在店里等待客人到来。(线程池) + +所有的客人都处于等待理发的状态,一个员工理发之后继续处于等待状态,为了激励员工,如果多理发的员工会得到更多的薪资(饥渴的线程),但是为了避免线程同事竞争同一个客户,我们将所有的客户放在一个房子里面(资源加锁),客户一个一个出来,只有一个员工可以帮客人理发。 + +![截屏2021-01-26 下午5.22.46](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-01-26%20%E4%B8%8B%E5%8D%885.22.46.png) + + + +![截屏2021-01-26 下午5.29.35](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-01-26%20%E4%B8%8B%E5%8D%885.29.35.png) + + + + + +23.thread_pool.c + +```c +#include "00.head.h" +#include "24.thread_pool.h" + +void task_queue_init(struct task_queue *taskQueue, int size) {//初始化队列 + taskQueue->size = size; + taskQueue->total = 0; + taskQueue->head = taskQueue->tail = 0; + pthread_mutex_init(&taskQueue->mutex, NULL); + pthread_cond_init(&taskQueue->cond, NULL); + taskQueue->data = calloc(size, sizeof(void *)); +} + +void task_queue_push(struct task_queue *taskQueue, char *str) { + pthread_mutex_lock(&taskQueue->mutex); + if (taskQueue->total == taskQueue->size) { + pthread_mutex_unlock(&taskQueue->mutex); + printf("task queue is full!\n"); + return ; + } + printf(":%s\n", str); + taskQueue->data[taskQueue->tail] = str; + taskQueue->total++; + if (++taskQueue->tail == taskQueue->size) { + printf("task queue reach end!\n"); + taskQueue->tail = 0; + } + pthread_cond_signal(&taskQueue->cond); + pthread_mutex_unlock(&taskQueue->mutex); +} + +char *task_queue_pop(struct task_queue *taskQueue) { + pthread_mutex_lock(&taskQueue->mutex); + while (taskQueue->total == 0) { + printf("task queue is empty!\n"); + pthread_cond_wait(&taskQueue->cond, &taskQueue->mutex); + } + char *str = taskQueue->data[taskQueue->head]; + printf(":%s\n", str); + taskQueue->total--; + if (++taskQueue->head == taskQueue->size) { + printf("task queue reach end!\n"); + taskQueue->head = 0; + } + pthread_mutex_unlock(&taskQueue->mutex); + return str; +} + +//循环打印 + +``` + +24.thread_pool.h + +```c + +struct task_queue{ + //循环队列 + int size; + int total; + int head; + int tail; + char **data; + pthread_mutex_t mutex;//加锁 + pthread_cond_t cond;//检测是否又客人 +}; + +void task_queue_init(struct task_queue *taskQueue, int size);//建立等待区,等待区又size大小 +void task_queue_push(struct task_queue *taskQueue, char *str);//str入队列 +char *task_queue_pop(struct task_queue *taskQueue);//str入队列 + + +``` + + + + + +```c +#include "00.head.h" +#include "24.thread_pool.h" + +#define THREAD 5 +#define QUEUE 50 + +void *do_work(void *arg) { + pthread_detach(pthread_self());//线程分离 + struct task_queue *taskQueue = (struct task_queue *)arg; + while (1) { + char *str = task_queue_pop(taskQueue); + printf("<%d> : %s !\n", pthread_self, str);//线程拿到自己的id + } + +} + +int main() { + pthread_t tid[THREAD]; + struct task_queue taskQueue; + task_queue_init(&taskQueue, QUEUE); + char buff[QUEUE][1024] = {0}; + + for (int i = 0; i < THREAD; i++) { + pthread_create(&tid[i], NULL, do_work, (void *) &taskQueue); + } + int sub = 0; + while (1) { + FILE *fp = fopen("./25.thread_pool_text.c", "r"); + if (fp == NULL) { + perror("fopen"); + exit(0); + } + while (fgets(buff[sub++], 1024, fp) != NULL) { + task_queue_push(&taskQueue, buff[sub]); + if (sub == QUEUE) { + //满了 + sub = 0; + } + if (taskQueue.total == taskQueue.size) { + // + while (1) { + if (taskQueue.total< taskQueue.size) { + break; + } + usleep(10000); + } + } + } + fclose(fp); + } + + return 0; +} + +``` + + + + + +```c +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +``` + + + + + +# 7.多线程编程基础 + +pthread_create产生线程 + +谁生的线程 + +线程模型 + +内核线程在内核上产生的 + +用户线程在用户空间上产生的 + + + +pthread_t线程ID + +判断线程是否相等? + +不能用线程ID判断,用pthread_equal + +pthread_exit()线程自杀 + +pthread_cancel线程自杀 + +exit() + +pthread_join()等待另一个线程结束, + +成功0, + +pthread_detach +标记一个分离的线程,线程结束后,所有的有关线程的内容都会被释放 + +同时分离同一个线程为被定义 + +attr线程属性 + +pthread_yield() + +让出处理器, + + + + + +# 8.socket编程 + +![截屏2021-01-30 下午5.39.09](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-01-30%20%E4%B8%8B%E5%8D%885.39.09.png) + +![截屏2021-01-29 下午3.54.54](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-01-29%20%E4%B8%8B%E5%8D%883.54.54.png) + +![截屏2021-01-29 下午4.06.49](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-01-29%20%E4%B8%8B%E5%8D%884.06.49.png) + + + +连接的本质是双方都保存一些变量,用来描述双方之间的状态 + + + + + +端口是一种计算机设备,就像一个大楼(计算机)里面的一个房间,一个大楼都会有一个独立的地址,可能会与多个地址,如果有多个地址,端口就是具体的房间后 + +![截屏2021-01-29 下午4.22.00](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-01-29%20%E4%B8%8B%E5%8D%884.22.00.png) + +序列号:第几回发送 + +确认应答号:确认收到1可以发送2了 + +字:32位,4字节=1字 + +字节 + +RST=reset重置连接 + +SYN=1,对方收到包,建立请求连接,如果正确恢复SYN + +SYN+ACK二次握手 + +ACK三次握手 + +FIN结束包 + +检验和:检验包是否有问题, + + + +![image-20210129163616346](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/image-20210129163616346.png) + + + + + +![截屏2021-01-29 下午4.40.49](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-01-29%20%E4%B8%8B%E5%8D%884.40.49.png) + + + +TCP的本质是字节流 + +listen切换套接字主动为被动 + +![截屏2021-01-29 下午4.46.27](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-01-29%20%E4%B8%8B%E5%8D%884.46.27.png) + +![截屏2021-01-29 下午4.54.09](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-01-29%20%E4%B8%8B%E5%8D%884.54.09.png) + +![截屏2021-01-29 下午5.02.56](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-01-29%20%E4%B8%8B%E5%8D%885.02.56.png) + + + +![截屏2021-01-29 下午5.04.51](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-01-29%20%E4%B8%8B%E5%8D%885.04.51.png) + + + +![截屏2021-01-29 下午5.14.09](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-01-29%20%E4%B8%8B%E5%8D%885.14.09.png) + + + +![截屏2021-01-29 下午5.20.19](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-01-29%20%E4%B8%8B%E5%8D%885.20.19.png) + + + +![截屏2021-01-29 下午5.22.03](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-01-29%20%E4%B8%8B%E5%8D%885.22.03.png) + + + +![截屏2021-01-29 下午5.24.01](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-01-29%20%E4%B8%8B%E5%8D%885.24.01.png) + +![截屏2021-01-29 下午5.25.23](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-01-29%20%E4%B8%8B%E5%8D%885.25.23.png) + + + +![截屏2021-01-29 下午5.25.44](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-01-29%20%E4%B8%8B%E5%8D%885.25.44.png) + + + +![截屏2021-01-29 下午5.26.03](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-01-29%20%E4%B8%8B%E5%8D%885.26.03.png) + + + + + + + +26.tcp.server.c + +```c +#include "00.head.h" + + +int socket_create(int port) { + int sockfd; + if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {//创建套接字 + return -1; + } + struct sockaddr_in addr; + addr.sin_family = AF_INET;//IPV4 + addr.sin_port = htons(port);//主机字节序转换成本地字节序 + addr.sin_addr.s_addr = inet_addr("0.0.0.0"); + if (bind(sockfd, (struct sockaddr *)&addr, sizeof(addr)) < 0) { + return -1; + } + + if (listen(sockfd, 20) < 0) { + return -1; + } + return sockfd; +} +``` + + + +27.tcp.server.h + +```c + +//定义函数接口 +int socket_create(int port);//创建套接字,参数端口 +``` + + + +28.tcp.client.h + +```c +int socket_connect(char *ip, int port); +``` + + + +29.tcp.client.c + +```c +#include "00.head.h" + +int socket_connect(char *ip, int port) { + int sockfd; + if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) { + return -1; + } + struct sockaddr_in server; + server.sin_family = AF_INET; + server.sin_port = htons(port); + server.sin_addr.s_addr = inet_addr(ip); + + if (connect(sockfd, (struct sockaddr *)&server, sizeof(server)) < 0) { + return -1; + } + return sockfd; +} +``` + + + +30.tcp.main.server.c + +```c +#include "00.head.h" + +int main(int argc, char **argv) { + int server_listen, sockfd, port; + if (argc != 2) { + fprintf(stderr, "Usage:%s port!\n", argv[0]); + exit(1); + } + port = atoi(argv[1]); + if ((server_listen = socket_create(port)) < 0) { + perror("socket_create"); + exit(-1); + } + while (1) { + if ((sockfd = accept(server_listen, NULL, NULL)) < 0) { + perror("accept"); + exit(1); + } + printf("Something is online\n"); + pid_t pid; + if ((pid = fork()) < 0) { + exit(-1); + } + if (pid == 0) {//支持并发 + while (1) { + char buff[512] = {0}; + recv(sockfd, buff, sizeof(buff), 0); + printf("recv : %s\n", buff); + } + } + } + + + return 0; +} + +``` + + + +32.tcp.main.client.c + +```c +#include "00.head.h" + + +int main(int argc, char **argv) { + int sockfd, port; + char buff[512] = {0}, ip[20] = {0}; + if (argc != 3) { + fprintf(stderr, "Usage:%s is port!\n", argv[0]); + } + strcpy(ip, argv[1]); + port = atoi(argv[2]); + if ((sockfd = socket_connect(ip, port)) < 0) { + perror("socket_connect"); + exit(1); + } + printf("before loop!\n"); + while (1) { + scanf("%[^\n]s", buff); + getchar(); + if (!strlen(buff)) continue; + send(sockfd, buff, strlen(buff), 0); + bzero(buff, sizeof(buff)); + } + + + return 0; +} +``` + + + + + +```shell +gcc 30.tcp.main.server.c 26.tcp.server.c -o 31.tcp.server +gcc 32.tcp.main.client.c 29.tcp.client.c -o 33.tcp.client + +./31.tcp.server 9000 [130] +Something is online +recv : hello +^C + +./33.tcp.client 8.129.127.2 9000 +before loop! +hello +^C +``` + + + + + +# 输入一个网址发生了什么? + + + +端口复用: + +from IP + +from port + +to ip + +to port + + + +singnal + +```c +//26.tcp.server.c +#include "00.head.h" + + +int socket_create(int port) {//创建套接字 + int sockfd; + if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {//创建套接字,ipv4,socker流 + return -1;//有太多的文件 + } + struct sockaddr_in addr; + addr.sin_family = AF_INET;//协议组 IPV4 + addr.sin_port = htons(port);//端口,主机字节序转换成本地字节序 + addr.sin_addr.s_addr = inet_addr("0.0.0.0");// + if (bind(sockfd, (struct sockaddr *)&addr, sizeof(addr)) < 0) {//绑定 + return -1;//端口被占用 + } + + if (listen(sockfd, 20) < 0) {// + return -1; + } + return sockfd; +} + +//27.tcp.server.h +//定义函数接口 +int socket_create(int port);//创建套接字,参数端口 + +//30.tcp.main.server.c +#include "00.head.h" + +int main(int argc, char **argv) { + int server_listen, sockfd, port; + if (argc != 2) { + fprintf(stderr, "Usage:%s port!\n", argv[0]); + exit(1); + } + port = atoi(argv[1]); + if ((server_listen = socket_create(port)) < 0) {//创建套接字 + perror("socket_create"); + exit(-1); + } + struct sockaddr_in client; + socklen_t len = sizeof(client); + while (1) {//用于等待接收数据 + if ((sockfd = accept(server_listen, (struct sockaddr *)&client, &len)) < 0) {//接受连接 + perror("accept");//三次握手失败 + exit(1); + } + printf("<%s> is online\n", inet_ntoa(client.sin_addr)); + pid_t pid; + if ((pid = fork()) < 0) { + exit(-1); + } + if (pid == 0) { + close(server_listen); + while (1) {// + char buff[512] = {0}; + char tobuff[1024] = {0}; + size_t ret = recv(sockfd, buff, sizeof(buff), 0); + if (ret <= 0) { + printf("<%s> is offline\n", inet_ntoa(client.sin_addr)); + perror("recv"); + close(sockfd); + exit(1); + } + printf("recv <%s>: %s\n", inet_ntoa(client.sin_addr), buff); + sprintf(tobuff, "I've recvd your message : %s!\n", buff); + send(sockfd, tobuff, strlen(tobuff), 0);//发送给客户端 + } + } else { + printf(" : 什么也不gan。。。\n"); + close(sockfd);// + } + } + + + return 0; +} + +gcc 30.tcp.main.server.c 26.tcp.server.c -o 31.tcp.server +``` + + + + + +```c +//28.tcp.client.h +int socket_connect(char *ip, int port); + +//29.tcp.client.c +#include "00.head.h" + +int socket_connect(char *ip, int port) { + int sockfd; + if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) { + return -1; + } + struct sockaddr_in server; + server.sin_family = AF_INET; + server.sin_port = htons(port); + server.sin_addr.s_addr = inet_addr(ip); + + if (connect(sockfd, (struct sockaddr *)&server, sizeof(server)) < 0) { + return -1; + } + return sockfd; +} + +//32.tcp.main.client.c +#include "00.head.h" + +int sockfd; +void logout(int signum) { + close(sockfd); + printf("ByeBye\n"); + exit(0); +} + +int main(int argc, char **argv) { + int port; + char buff[512] = {0}, ip[20] = {0}; + if (argc != 3) { + fprintf(stderr, "Usage:%s is port!\n", argv[0]); + } + strcpy(ip, argv[1]); + port = atoi(argv[2]); + signal(SIGINT, logout); + if ((sockfd = socket_connect(ip, port)) < 0) { + perror("socket_connect"); + exit(1); + } + printf("before loop!\n"); + while (1) { + printf(" : "); + scanf("%[^\n]s", buff); + getchar(); + if (!strlen(buff)) continue; + send(sockfd, buff, strlen(buff), 0); + printf("sending %s ...\n", buff); + bzero(buff, sizeof(buff)); + //sleep(1); + recv(sockfd, buff, sizeof(buff), 0); + printf(" : %s\n", buff); + bzero(buff, sizeof(buff)); + } + + + return 0; +} + +gcc 32.tcp.main.client.c 29.tcp.client.c -o 33.tcp.client +``` + + + +```shell +./31.tcp.server 9000 +<110.176.185.233> is online + : 什么也不gan。。。 +recv <110.176.185.233>: hello +recv <110.176.185.233>: hello 古子秋! +<110.176.185.233> is offline +recv: Success + +./33.tcp.client 8.129.127.2 9000 +before loop! + : hello +sending hello ... + : I've recvd your message : hello! + + : hello 古子秋! +sending hello 古子秋! ... + : I've recvd your message : hello 古子秋!! + + : ^CByeBye +``` + + + + + +# 二、计算机网络 + +# 9.计算机网络概论 + +![截屏2021-02-03 下午9.51.43](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-03%20%E4%B8%8B%E5%8D%889.51.43.png) + +![截屏2021-02-03 下午9.52.32](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-03%20%E4%B8%8B%E5%8D%889.52.32.png) + + + +![截屏2021-02-03 下午10.07.17](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-03%20%E4%B8%8B%E5%8D%8810.07.17.png) + +封装: + +![截屏2021-02-03 下午10.12.37](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-03%20%E4%B8%8B%E5%8D%8810.12.37.png) + +![截屏2021-02-03 下午10.06.55](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-03%20%E4%B8%8B%E5%8D%8810.06.55.png) + +![截屏2021-02-03 下午10.15.28](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-03%20%E4%B8%8B%E5%8D%8810.15.28.png) + +![截屏2021-02-03 下午10.20.09](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-03%20%E4%B8%8B%E5%8D%8810.20.09.png) + + + + + + + +语义:某个句子是什么意思 + +语法: + +同步:事件发生的顺序有迹可循,eg.三次挥手,具体都做什么 + + + + + +## 分组交换 + +分组 + +分包(packet)有路由协议决定 + + + + + +![截屏2021-02-03 下午10.31.51](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-03%20%E4%B8%8B%E5%8D%8810.31.51.png) + + + + + + + +![截屏2021-02-03 下午10.42.08](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-03%20%E4%B8%8B%E5%8D%8810.42.08.png) + +转发表:进来的数据计算要去的地方 + + + +## 电路交换 + +![截屏2021-02-03 下午10.44.20](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-03%20%E4%B8%8B%E5%8D%8810.44.20.png) + +接线员 + + + + + + + +![截屏2021-02-03 下午10.46.27](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-03%20%E4%B8%8B%E5%8D%8810.46.27.png) + + + +FDM + + + + + + + +![截屏2021-02-03 下午10.46.49](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-03%20%E4%B8%8B%E5%8D%8810.46.49.png) + + + + + +## 分组交换与电路交换的对比 + +分组:提供更好的带宽共享,成本低, + + + +## 时延掉包吞吐率 + +![截屏2021-02-03 下午10.49.59](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-03%20%E4%B8%8B%E5%8D%8810.49.59.png) + +结点处理时延:处理包的时间 + +排队时延:进入端口,等待处理的时间 + +传输时延:一个包发到网络上的时间 + +传播时延:信号经由主机出去,到达对方主机后,再回来经历的传输介质中传输的时间 + + + + + +![截屏2021-02-03 下午10.56.00](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-03%20%E4%B8%8B%E5%8D%8810.56.00.png) + +什么情况下丢包越来越严重 + +a分组到达队列的平均速率,所有分组都由Lbit构成,R为传输速率,La/R--->1 + +单位时间内到达的比特率 + + + + + +吞吐量 + + + + + +![截屏2021-02-03 下午10.58.51](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-03%20%E4%B8%8B%E5%8D%8810.58.51.png) + +取决于业务链路上传输速率最小的那个 + + + + + +# 10.应用层 + + + +应用层协议 + +![截屏2021-02-04 上午9.12.46](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-04%20%E4%B8%8A%E5%8D%889.12.46.png) + + + +P2P端到端:百度网盘 + +CS:王者荣耀、QQ. + +![截屏2021-02-04 上午9.13.50](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-04%20%E4%B8%8A%E5%8D%889.13.50.png) + +![截屏2021-02-04 上午9.15.18](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-04%20%E4%B8%8A%E5%8D%889.15.18.png) + + + + + +![截屏2021-02-04 上午9.17.29](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-04%20%E4%B8%8A%E5%8D%889.17.29.png) + +![截屏2021-02-04 上午9.29.03](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-04%20%E4%B8%8A%E5%8D%889.29.03.png) + + + + + +![截屏2021-02-04 上午9.29.56](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-04%20%E4%B8%8A%E5%8D%889.29.56.png) + +![截屏2021-02-04 上午9.30.31](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-04%20%E4%B8%8A%E5%8D%889.30.31.png) + + + +![截屏2021-02-04 上午9.30.49](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-04%20%E4%B8%8A%E5%8D%889.30.49.png) + +可靠:一定送到 + +吞吐量:大小 + +定时: + +安全性: + + + +![截屏2021-02-04 上午9.34.15](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-04%20%E4%B8%8A%E5%8D%889.34.15.png) + + + + + +![截屏2021-02-04 上午9.36.12](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-04%20%E4%B8%8A%E5%8D%889.36.12.png) + + + + + +![截屏2021-02-04 上午9.36.04](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-04%20%E4%B8%8A%E5%8D%889.36.04.png) + + + +![截屏2021-02-04 上午9.36.23](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-04%20%E4%B8%8A%E5%8D%889.36.23.png) + + + +![截屏2021-02-04 上午9.37.25](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-04%20%E4%B8%8A%E5%8D%889.37.25.png) + + + + + +## HTTP与WEB协议 + + + +![截屏2021-02-04 上午9.51.51](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-04%20%E4%B8%8A%E5%8D%889.51.51.png) + +![截屏2021-02-04 上午9.52.27](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-04%20%E4%B8%8A%E5%8D%889.52.27.png) + +URL统一资源定位符 + +web浏览器:解析资源 + +web服务器:tomcat等 + + + +![截屏2021-02-04 上午9.54.21](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-04%20%E4%B8%8A%E5%8D%889.54.21.png) + + + + + +![截屏2021-02-04 上午9.54.56](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-04%20%E4%B8%8A%E5%8D%889.54.56.png) + + + +![截屏2021-02-04 上午9.56.47](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-04%20%E4%B8%8A%E5%8D%889.56.47.png) + +![截屏2021-02-04 上午9.58.45](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-04%20%E4%B8%8A%E5%8D%889.58.45.png) + + + +**为什么是三次握手?不是两次?不是四次?** + + + +![截屏2021-02-04 上午10.00.33](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-04%20%E4%B8%8A%E5%8D%8810.00.33.png) + +![截屏2021-02-04 上午10.02.52](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-04%20%E4%B8%8A%E5%8D%8810.02.52.png) + + + + + +![截屏2021-02-04 上午10.03.04](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-04%20%E4%B8%8A%E5%8D%8810.03.04.png) + + + +![截屏2021-02-04 上午10.03.50](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-04%20%E4%B8%8A%E5%8D%8810.03.50.png) + +![截屏2021-02-04 上午10.06.07](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-04%20%E4%B8%8A%E5%8D%8810.06.07.png) + +![截屏2021-02-04 上午10.07.56](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-04%20%E4%B8%8A%E5%8D%8810.07.56.png) + + + +![截屏2021-02-04 上午10.15.34](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-04%20%E4%B8%8A%E5%8D%8810.15.34.png) + +![截屏2021-02-04 上午10.28.39](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-04%20%E4%B8%8A%E5%8D%8810.28.39.png) + + + +## HTTP协议和HTTPS协议 + +![截屏2021-02-05 下午1.37.44](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-05%20%E4%B8%8B%E5%8D%881.37.44.png) + +![截屏2021-02-05 下午1.38.09](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-05%20%E4%B8%8B%E5%8D%881.38.09.png) + +![截屏2021-02-05 下午1.41.05](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-05%20%E4%B8%8B%E5%8D%881.41.05.png) + +![截屏2021-02-05 下午1.43.47](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-05%20%E4%B8%8B%E5%8D%881.43.47.png) + + + +## FTP协议 + +在局域网传输 + +![截屏2021-02-05 下午1.45.36](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-05%20%E4%B8%8B%E5%8D%881.45.36.png) + + + +![截屏2021-02-05 下午1.47.03](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-05%20%E4%B8%8B%E5%8D%881.47.03.png) + + + +控制连接:传命令,指令,常连接 + +数据连接:传数据,短连接 + +为什么用两条:双连接使业务实现更加简单,eg:专线,指令确定什么时候结束,指令信息引发的数据传输不一定什么时候传输,需要预先判断,不确定下一步是命令还是数据,需要很步骤,如果有两个,不需要关注数据多大,不论文件多大,收到关闭结束就可以,性能得到提升, + +![截屏2021-02-05 下午1.59.27](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-05%20%E4%B8%8B%E5%8D%881.59.27.png) + + + + + +## SMTP、POP3、IMAP协议 + +![截屏2021-02-05 下午2.02.37](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-05%20%E4%B8%8B%E5%8D%882.02.37.png) + +使用TCP传输 + + + +## DNS协议 + +![截屏2021-02-05 下午2.17.10](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-05%20%E4%B8%8B%E5%8D%882.17.10.png) + +![截屏2021-02-05 下午2.27.31](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-05%20%E4%B8%8B%E5%8D%882.27.31.png) + +![截屏2021-02-05 下午2.35.24](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-05%20%E4%B8%8B%E5%8D%882.35.24.png) + +![截屏2021-02-05 下午2.36.43](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-05%20%E4%B8%8B%E5%8D%882.36.43.png) + + + +主机发送请求(1.guziqiu.com),先到本地DNS查找(没有),然后到根DNS查找(2.是顶级域名),回应去顶级域名查找(3.告知去com域名地址),顶级域名回应(4/5.告知到权威DNS查找),权威DNS阿里云维护(6/7告知IP地址),本地DNS存一份,为了下一次访问,(域名过期时间10min) + +本地递归,远程迭代 + +![截屏2021-02-05 下午3.11.25](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-05%20%E4%B8%8B%E5%8D%883.11.25.png) + +TTL域名有效时间:aliyun10min + + + + + + + + + + + +# 11.可靠传输协议的实现 + +## 运输层协议 + +![截屏2021-02-05 下午4.00.29](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-05%20%E4%B8%8B%E5%8D%884.00.29.png) + +![截屏2021-02-05 下午4.04.28](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-05%20%E4%B8%8B%E5%8D%884.04.28.png) + + + +![截屏2021-02-05 下午4.15.14](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-05%20%E4%B8%8B%E5%8D%884.15.14.png) + +![截屏2021-02-05 下午4.15.47](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-05%20%E4%B8%8B%E5%8D%884.15.47.png) + +![截屏2021-02-05 下午4.17.14](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-05%20%E4%B8%8B%E5%8D%884.17.14.png) + + + +## 为什么会选择无连接运输 + +![截屏2021-02-05 下午4.19.17](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-05%20%E4%B8%8B%E5%8D%884.19.17.png) + +UDP由自己控制,TCP有控制机制 + + + +## UDP + +![截屏2021-02-05 下午4.29.21](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-05%20%E4%B8%8B%E5%8D%884.29.21.png) + +![截屏2021-02-05 下午4.30.26](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-05%20%E4%B8%8B%E5%8D%884.30.26.png) + +![截屏2021-02-05 下午4.33.06](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-05%20%E4%B8%8B%E5%8D%884.33.06.png) + + + +## RDT可靠传输协议 + +信道可靠,上端不用考虑是否可靠,数据一定可靠 + +可靠的数据协议通过不可靠的信道 + +![截屏2021-02-05 下午4.36.31](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-05%20%E4%B8%8B%E5%8D%884.36.31.png) + +![截屏2021-02-05 下午4.37.10](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-05%20%E4%B8%8B%E5%8D%884.37.10.png) + + + +具有比特差错信道:能感知到比特级的错误 + +![截屏2021-02-05 下午4.39.20](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-05%20%E4%B8%8B%E5%8D%884.39.20.png) + +![截屏2021-02-05 下午4.43.41](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-05%20%E4%B8%8B%E5%8D%884.43.41.png) + +![截屏2021-02-05 下午4.44.56](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-05%20%E4%B8%8B%E5%8D%884.44.56.png) + +![截屏2021-02-05 下午4.46.51](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-05%20%E4%B8%8B%E5%8D%884.46.51.png) + +![](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-05%20%E4%B8%8B%E5%8D%884.46.51.png) + +![截屏2021-02-05 下午4.50.43](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-05%20%E4%B8%8B%E5%8D%884.50.43.png) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +# 12.TCP协议 + +## 12.1 报文 + +通过套接字使用底层提供给我们的服务 + +![截屏2021-02-05 下午5.17.37](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-05%20%E4%B8%8B%E5%8D%885.17.37.png) + +![截屏2021-02-05 下午5.18.30](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-05%20%E4%B8%8B%E5%8D%885.18.30.png) + + + +序号:字节流序号(非连续) + +确认号:希望得到的下一个正确得到的序号 + + + +首部长度4bit + +接收窗口的大小: + +URG:紧急数据, + +PSH:立即上交,收到数据立马上交应用层 + +ACK:确认码=1,对方发过来的确认 + +RST:连接远程服务器,拒绝连接,对方没有打开端口 + +SYN:同步,三次握手 + +FIN:四次挥手 + + + + + +![截屏2021-02-06 下午4.22.41](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-06%20%E4%B8%8B%E5%8D%884.22.41.png) + +用户键入x产生数据报,发送到服务器, + + + + + +![截屏2021-02-06 下午4.26.07](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-06%20%E4%B8%8B%E5%8D%884.26.07.png) + + + +![截屏2021-02-06 下午4.59.42](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-06%20%E4%B8%8B%E5%8D%884.59.42.png) + +主要针对双方 + + + +## 12.2 三次握手 + +![截屏2021-02-06 下午5.05.55](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-06%20%E4%B8%8B%E5%8D%885.05.55.png) + +为什么是三次握手: + +因为双方都能确定能发送能接收,如果是两次,客户端能发能收,服务端无法确定发送的消息能否被收到,四次也可以,但是多余 + + + +## 12.3 四次挥手 + +![截屏2021-02-06 下午5.10.57](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-06%20%E4%B8%8B%E5%8D%885.10.57.png) + + + +为什么要四次: + +客户端发了FIN,服务端没有返回ACK,服务端发送FIN,客户端没有回复ACK,半关闭连接,连接还在,生成一种特殊状态的套接字,有害的,占用套接字 + +为什么发起方多等待30s~2min,两个最大生命周期: + +1.避免ACK丢失,服务器会发送ACK,(有可能发送给对方的ACK丢失) + +2.避免刚断开连接,(四次挥手完成)随机新端口(和之前的一样,端口重用)跑起来之后,收到之前已经断开的端口发送的FIN包或者数据,新端口数据还没有发送完成就断开,所以需要四次挥手 + + + +## 12.4 TCP状态转换图 + +![截屏2021-02-06 下午5.23.48](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-06%20%E4%B8%8B%E5%8D%885.23.48.png) + +![截屏2021-02-06 下午5.29.26](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-06%20%E4%B8%8B%E5%8D%885.29.26.png) + + + +## 12.5 拥塞控制方法 + +![截屏2021-02-06 下午5.32.58](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-06%20%E4%B8%8B%E5%8D%885.32.58.png) + + + + + +![截屏2021-02-06 下午5.34.18](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-06%20%E4%B8%8B%E5%8D%885.34.18.png) + +资源管理信元:每32个信元有一个资源管理信元,返回拥塞状态 + + + + + +![截屏2021-02-06 下午5.40.49](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-06%20%E4%B8%8B%E5%8D%885.40.49.png) + + + +![截屏2021-02-06 下午5.42.32](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-06%20%E4%B8%8B%E5%8D%885.42.32.png) + + + + + +## 12.6 拥塞控制:慢启动 + +![截屏2021-02-06 下午5.45.03](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-06%20%E4%B8%8B%E5%8D%885.45.03.png) + +MMS最大报文段长度,TCP一次放的最大数据量 + +RTT往返时间 + +![截屏2021-02-06 下午5.48.47](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-06%20%E4%B8%8B%E5%8D%885.48.47.png) + + + +发送超时为什么会变成1MSS,合理吗,为什么 + +不合理, + +为什么3个冗余ACK快速重传:包出问题 + + + + + +![截屏2021-02-06 下午5.49.58](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-06%20%E4%B8%8B%E5%8D%885.49.58.png) + + + +TCP拥塞控制流程转换 + +![截屏2021-02-06 下午5.52.11](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-06%20%E4%B8%8B%E5%8D%885.52.11.png) + +慢启动:收到冗余ACK, + + + + + + + + + +# 13.IP地址 + + + +## 13.1 IP地址的基本结构 + +![截屏2021-02-24 下午5.53.10](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-24%20%E4%B8%8B%E5%8D%885.53.10.png) + + + +## 13.2 MAC地址 + +![截屏2021-02-24 下午8.34.52](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-24%20%E4%B8%8B%E5%8D%888.34.52.png) + + + +网卡:网络适配器 + +## 13.3 IP地址的分类 + +![截屏2021-02-24 下午8.40.58](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-24%20%E4%B8%8B%E5%8D%888.40.58.png) + + + +本地通讯靠吼,子网掩码来区分是否在一个子网中 + +广播: + + + +## 13.4 ARP协议 + +![截屏2021-02-24 下午8.55.17](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-24%20%E4%B8%8B%E5%8D%888.55.17.png) + + + +ARP:将IP地址转换为MAC地址 + + + +# 14 网络层 + + + +## 14.1 转发和路由选择 + +![截屏2021-02-24 下午9.03.04](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-24%20%E4%B8%8B%E5%8D%889.03.04.png) + + + +![截屏2021-02-24 下午9.06.14](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-24%20%E4%B8%8B%E5%8D%889.06.14.png) + + + + + +## 14.2 路由选择算法 + + + +![截屏2021-02-24 下午9.08.24](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-24%20%E4%B8%8B%E5%8D%889.08.24.png) + + + +路由选择算法决定整个网络范围内的端到端的路径选择 + + + +转发表决定本地路由器内部转发的出口 + + + +## 14.3 分组网络 + +![截屏2021-02-24 下午9.15.21](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-24%20%E4%B8%8B%E5%8D%889.15.21.png) + + + +虚拟电路网络 + +![截屏2021-02-24 下午9.20.18](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-24%20%E4%B8%8B%E5%8D%889.20.18.png) + + + +![截屏2021-02-24 下午9.20.55](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-24%20%E4%B8%8B%E5%8D%889.20.55.png) + + + +![截屏2021-02-24 下午9.26.29](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-24%20%E4%B8%8B%E5%8D%889.26.29.png) + + + +## 14.4 路由器 + +![截屏2021-02-24 下午9.29.32](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-24%20%E4%B8%8B%E5%8D%889.29.32.png) + + + +交换结构:将出入端口输出到输出端口 + +路由选择处理器:只负责和 其他的路由器或者上层的管理软件 交互 + + + +![截屏2021-02-24 下午9.34.09](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-24%20%E4%B8%8B%E5%8D%889.34.09.png) + +线路端接:将传入到端口的信号转换成电信号 + + + +![截屏2021-02-24 下午9.39.13](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-24%20%E4%B8%8B%E5%8D%889.39.13.png) + +内存式:效率最低 + +总线:电信号可能抵消 + +纵横式: + + + +![截屏2021-02-24 下午9.43.46](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-24%20%E4%B8%8B%E5%8D%889.43.46.png) + + + +排队:可能会发生数据丢失,丢包 + +排队时延不可控 + + + +## 14.5 网络层内容图 + +![截屏2021-02-24 下午9.46.02](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-24%20%E4%B8%8B%E5%8D%889.46.02.png) + + + + + +## 14.6 IP数据报 + +![截屏2021-02-24 下午9.49.14](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-24%20%E4%B8%8B%E5%8D%889.49.14.png) + + + +寿命TTL:经过一个路由器TTL-1,避免路由器找不到目的地,经过一个路由器TTL-1,最终消亡在网络中 + + + +## 14.7 DHCP + +![截屏2021-02-24 下午9.52.13](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-24%20%E4%B8%8B%E5%8D%889.52.13.png) + + + +连上wifi时发生了什么事情? + +接入wifi后,就可以和网络中的其他设备通信,MAC地址不变,通过MAC地址申请一个可以用的IP地址,接入网络后,通过广播dest请求地址,DHCP server收到请求后发送DHCP offer,可以有多个DHCP offer(多个DHCP服务器同时发送),接收offer,发送请求IP地址,DHCP server确定,所有的报文都是广播,所有本地的设备都会知道他的IP地址 + + + + + +## 14.8 网络地址转换NAT + + + +![截屏2021-02-24 下午10.04.00](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-24%20%E4%B8%8B%E5%8D%8810.04.00.png) + + + + + + + +# 15.ICMP协议 + +![截屏2021-02-26 下午4.54.49](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-26%20%E4%B8%8B%E5%8D%884.54.49.png) + +ping + + + +![截屏2021-02-26 下午4.55.13](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-26%20%E4%B8%8B%E5%8D%884.55.13.png) + +网络层ICMP尝试连接目的主机,但是无法到达 + + + +![截屏2021-02-26 下午4.56.46 1](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-26%20%E4%B8%8B%E5%8D%884.56.46%201.png) + + + +u - v 1跳 + + + +D路由器的路由表 + +![截屏2021-02-27 下午4.39.35](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-27%20%E4%B8%8B%E5%8D%884.39.35.png) + +D->W 下一跳是A,需要两跳 + +![截屏2021-02-27 下午4.46.53](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-27%20%E4%B8%8B%E5%8D%884.46.53.png) + +需要一定的时间才可以收到改变,出现更新不及时的现象,网络波动 + + + + + + + +开放最短优先 + +![截屏2021-02-27 下午4.48.15](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-27%20%E4%B8%8B%E5%8D%884.48.15.png) + +范围的同时都可以收到 + + + + + +![截屏2021-02-27 下午4.51.52](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-27%20%E4%B8%8B%E5%8D%884.51.52.png) + +边界网关协议BGP:在自治系统和自治系统之间建立一条扩展的会话,它是一跳真实的TCP连接,这个连接会一直同步两段路由器的信息(如3a,和1c交换他两所知道的路由信息,及时获取自治系统内部的信息,TCP长连接) + + + + + + + +# 重点:DHCP、NAT、IP数据报、路由器、转发表和转发 + + + + + +# 16.链路层 + +## 16.1网络适配器 + +![截屏2021-02-27 下午5.01.39](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-27%20%E4%B8%8B%E5%8D%885.01.39.png) + +主要设备:网络适配器 即 网卡 + + + +## 16.2链路层提供的服务 + +![截屏2021-02-27 下午5.01.51](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-27%20%E4%B8%8B%E5%8D%885.01.51.png) + +链路层也可以实现可靠交付 + + + +> 为什么我们不在数据链路层实现可靠交付? +> +> 如果在数据链路层实现可靠传输,那么在网络层、传输层、应用层也就实现了可靠传输, 不可靠主要是因为物理链路不可靠 +> +> 如果要在数据链路层实现可靠交付,就得超时重传、序号、确认ACK、差错检验,通信效率降低 + + + +## 16.3差错检测 + +![截屏2021-02-27 下午5.11.21](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-27%20%E4%B8%8B%E5%8D%885.11.21.png) + + + + + +![截屏2021-02-27 下午5.12.02](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-27%20%E4%B8%8B%E5%8D%885.12.02.png) + + + + + +![截屏2021-02-27 下午5.13.48](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-27%20%E4%B8%8B%E5%8D%885.13.48.png) + + + +## 16.4链路层协议集 + +![截屏2021-02-27 下午5.17.37](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-27%20%E4%B8%8B%E5%8D%885.17.37.png) + +链路层协议集:描述了链路层做了什么事情 + + + + + +## 16.5信道划分协议 + +![截屏2021-02-27 下午5.20.52](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-27%20%E4%B8%8B%E5%8D%885.20.52.png) + + + +## 16.6 信道划分 CDMA协议 + +![截屏2021-02-27 下午5.25.25](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-27%20%E4%B8%8B%E5%8D%885.25.25.png) + + + + + + + +![截屏2021-02-27 下午5.43.59](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-27%20%E4%B8%8B%E5%8D%885.43.59.png) + + + + + +![截屏2021-02-27 下午5.48.23](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-27%20%E4%B8%8B%E5%8D%885.48.23.png) + + + +两个发送者 + +![截屏2021-02-27 下午5.50.55](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-27%20%E4%B8%8B%E5%8D%885.50.55.png) + + + + + +## 16.7 随机接入协议 + +![截屏2021-02-27 下午6.01.52](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-27%20%E4%B8%8B%E5%8D%886.01.52.png) + + + +![截屏2021-02-28 下午7.37.17](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-28%20%E4%B8%8B%E5%8D%887.37.17.png) + + + +![截屏2021-02-28 下午7.39.10](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-28%20%E4%B8%8B%E5%8D%887.39.10.png) + + + + + +## 16.8 载波侦听多路访问 CSMA/CD + +![截屏2021-02-28 下午7.41.51](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-28%20%E4%B8%8B%E5%8D%887.41.51.png) + +![截屏2021-02-28 下午7.47.36](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-28%20%E4%B8%8B%E5%8D%887.47.36.png) + + + +## 16.9 轮流协议 轮询协议 令牌传递协议 + +![截屏2021-02-28 下午7.48.48](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-28%20%E4%B8%8B%E5%8D%887.48.48.png) + + + + + + + +![截屏2021-02-28 下午7.50.22](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-28%20%E4%B8%8B%E5%8D%887.50.22.png) + + + +## 16.10 以太网技术 + +![截屏2021-02-28 下午7.51.16](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-28%20%E4%B8%8B%E5%8D%887.51.16.png) + + + +## 16.11 交换机表 + +![截屏2021-02-28 下午8.03.40](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-28%20%E4%B8%8B%E5%8D%888.03.40.png) + +MAC地址,接口 + +路由器工作在网络层,通过转发表转发,到达路由器的输出链路。 + +交换机:一般指数据链路层的交换机,交换机里面有交换机表 + + + +集线器:扩展坞 + +网桥:网络层 + + + +## 通电后局域网内部发生了什么事情? + +有线网络:机器检测到链路正常可以交互,缺IP地址,需要用DHCP协议(应用层)在网络中申请一个IP地址 ,通过广播的形式发送给所有人,数据链路层收到包后发出去,交换机数据链路层收到包,并且知道广播的人是谁,解开包发现是一个广播包(目的地址是fffff),给他的接口的每一个机器发一个包,其中一个人是DHCP服务器,DHCP服务器收到后回一个包,以广播的形式发送,交换机收到后更新交换机表,并且以广播的形式发送,然后机器拿到了IP地址。 + +## 此时访问一个网站会发生什么事情? + +首先需要做DNS解析,首先是本地host文件,直接从本地host文件解析网站找到IP地址,没找到就会用DNS,本地DNS,DNS是对DHCP服务器请求时由DHCP服务器告知的。跟DHCP服务器通讯,拿到一个IP(谷歌8.8.8.8),给谷歌DNS发数据,通过子网掩码判断是否在一个子网中,不在掩码中,然后就要去网关(即找到路由器),包先到达交换机,如果不知道就广播形式发送给每一个机器,找到后对照路由表查询出口,从输出链路出来之后,最后从DNS拿到一个IP地址(百度的IP地址),发送HTTP请求,底层TCP,TCP三次握手, + +第一个数据报经过哪些服务器,贴上目标端口后交给IP层,网络层贴上目标IP地址,通过子网掩码判断是外网,到数据链路层,贴网关地址(通过ARP协议找到网关),通过有线连接,直接到达交换机的某一个口,交换机进行转发,转发到服务器,(百度机房路由器收到包,出去后到达主机,解析后发现目的主机是他,原地址不是我的地址,我的地址是内网地址,他看到的是经过层层转发得到的一个公网IP地址以及一个对应的端口,)去掉头,交给运输层,运输层发现目的端口是80,是给HTTP服务器,然后交给HTTP,HTTP发现源端口是8731(通讯端口),HTTP报文,get方法,资源是index.html,从本地读磁盘(一般会会在缓冲里面,资源比较火热),拿到文件后封装到报文,头源端口是80,目的端口是8731,给网络层,贴上IP地址,交给数据链路层,发现不和本地服务器在一个网络,交给网关,然后经过一层一层到了我的NAT服务器,发现端口是8731,一层一层到了我的主机,然后呈现在我的电脑上面 + + + +过程中发生了交换机自学习(更新交换机表),信息过滤(包发送到目标端口),交换机表的时间是什么时候学习到的数据,可能会过期,过期后会删除重新学习。 + + + + + + + + + + + + + +# 三、网络和系统编程 + + + + + +# 17. IO多路复用 + +## 17.1 select + +```cpp +NAME + select, pselect, FD_CLR, FD_ISSET, FD_SET, FD_ZERO - synchronous I/O multiplexing + +SYNOPSIS + /* According to POSIX.1-2001, POSIX.1-2008 */ + #include + + /* According to earlier standards */ + #include + #include + #include + + int select(int nfds, fd_set *readfds, fd_set *writefds, + fd_set *exceptfds, struct timeval *timeout); + + void FD_CLR(int fd, fd_set *set); + int FD_ISSET(int fd, fd_set *set); + void FD_SET(int fd, fd_set *set); + void FD_ZERO(fd_set *set); + + #include + + int pselect(int nfds, fd_set *readfds, fd_set *writefds, + fd_set *exceptfds, const struct timespec *timeout, + const sigset_t *sigmask); + + Feature Test Macro Requirements for glibc (see feature_test_macros(7)): + + pselect(): _POSIX_C_SOURCE >= 200112L +``` + + + + + +select 和 pselect 允许监控多个文件描述符,等待直到,或多个变成ready,(可以执行一个相关IO操作如:读) + +select 和 pselect时间结构体不一样,pselect 有信号掩码sigmask, + +三个独立的文件描述符集合,将会被监控查看是否有一些字符变得就绪,即读操作是否非阻塞的完成, + +信号掩码:收到信号有三种处理方法: + + + +## 17.2 poll ppoll epoll + +epoll红黑树实现 + + + +poll + + + +> 指针和数组有什么区别和关联? + +> 指针如果作为函数的指针会退化为数组, + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +# 0000000 + + + + + + + +# 0作业 + + + +## 0.1 作业1:ls实现 + +![截屏2021-01-05 上午11.11.26](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-01-05%20%E4%B8%8A%E5%8D%8811.11.26.png) + + + + + +``` + +``` + + + + + +## 0.2 ls作业->opendir() + +> opendir() +> +> readdir() +> +> closedir() +> +> ftell() +> +> readdir() + + + + + +man getpwpid getpwuid getgrgid + + + + + + + +0 + +锁:数据保护,互斥, + +![fc73bb52fe85013f9d2b4713814cfa29](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/fc73bb52fe85013f9d2b4713814cfa29.jpg) + + + +```c +#include "00.head.h" +//#define INS 5 + +char num_file[] = "./.num"; +char lock_file[] = "./.lock"; +struct Msg { + int now; + int sum; +}; + +struct Msg num; + +size_t set_num(struct Msg *msg) { + FILE *f = fopen(num_file, "w"); + if (f == NULL) { + fclose(f); + perror("fopen"); + return -1; + } + size_t nwrite = fwrite(msg, 1, sizeof(struct Msg), f); + fclose(f); + return nwrite; +} + +size_t get_num(struct Msg *msg) { + FILE *f = fopen(num_file, "r"); + if (f == NULL) { + fclose(f); + perror("fopen()"); + return -1; + } + size_t nread = fread(msg, 1, sizeof(struct Msg), f); + fclose(f); + return nread; +} + +void do_add(int end, int pid_num) { + while (1) { + FILE *lock = fopen(lock_file, "w"); + if (lock == NULL) { + perror("fopen()"); + exit(1); + } + flock(lock->_fileno, LOCK_EX); + if (get_num(&num) < 0) { + fclose(lock); + continue; + } + + if (num.now > end) { + fclose(lock); + break; + } + num.sum += num.now; + num.now++; + printf("the %dth child : now = %d, sum = %d\n", pid_num, num.now, num.sum); + set_num(&num); + flock(lock->_fileno, LOCK_UN); + fclose(lock); + } + +} + +int main(int argc, char *argv[]) { + + int opt, start = 0, end = 0, ins = 0; + while ((opt = getopt(argc, argv, "s:e:i:")) != -1) { + switch (opt) { + case 's': + start = atoi(optarg); + break; + case 'e': + end = atoi(optarg); + break; + case 'i': + ins = atoi(optarg); + break; + default: + fprintf(stderr, "Usage : %s -s start_num -e end_num -i\n", argv[0]); + exit(1); + } + } + printf("start = %d\n end = %d\n", start, end); + + num.now = 0; + num.sum = 0; + set_num(&num); + + pid_t pid; + int x = 0; + for (int i = 1; i <= ins; i++) { + if ((pid = fork()) < 0) { + perror("fork()"); + exit(-1); + } + if (pid == 0) {//孩子 + x = i; + break; + } + } + if (pid != 0) { + for (int i = 0; i < ins; i++) { + wait(NULL); + } + + //printf("I am father!\n"); + //sleep(100); + } else { + do_add(end, x); + } + get_num(&num); + printf("sum = %d\n", num.sum); + + return 0; +} +``` + +```shell +./a.out -s 0 -e 100 -i 10 [0] +start = 0 + end = 100 +the 1th child : now = 1, sum = 0 +the 7th child : now = 2, sum = 1 +the 1th child : now = 3, sum = 3 +the 7th child : now = 4, sum = 6 +the 1th child : now = 5, sum = 10 +the 7th child : now = 6, sum = 15 +the 1th child : now = 7, sum = 21 +the 7th child : now = 8, sum = 28 +the 1th child : now = 9, sum = 36 +the 7th child : now = 10, sum = 45 +the 1th child : now = 11, sum = 55 +the 7th child : now = 12, sum = 66 +the 7th child : now = 13, sum = 78 +the 7th child : now = 14, sum = 91 +the 1th child : now = 15, sum = 105 +the 7th child : now = 16, sum = 120 +the 1th child : now = 17, sum = 136 +the 7th child : now = 18, sum = 153 +the 1th child : now = 19, sum = 171 +the 2th child : now = 20, sum = 190 +the 1th child : now = 21, sum = 210 +the 1th child : now = 22, sum = 231 +the 3th child : now = 23, sum = 253 +the 1th child : now = 24, sum = 276 +the 3th child : now = 25, sum = 300 +the 1th child : now = 26, sum = 325 +the 3th child : now = 27, sum = 351 +the 1th child : now = 28, sum = 378 +the 3th child : now = 29, sum = 406 +the 1th child : now = 30, sum = 435 +the 6th child : now = 31, sum = 465 +the 1th child : now = 32, sum = 496 +the 3th child : now = 33, sum = 528 +the 1th child : now = 34, sum = 561 +the 6th child : now = 35, sum = 595 +the 1th child : now = 36, sum = 630 +the 3th child : now = 37, sum = 666 +the 1th child : now = 38, sum = 703 +the 6th child : now = 39, sum = 741 +the 1th child : now = 40, sum = 780 +the 2th child : now = 41, sum = 820 +the 1th child : now = 42, sum = 861 +the 2th child : now = 43, sum = 903 +the 2th child : now = 44, sum = 946 +the 3th child : now = 45, sum = 990 +the 2th child : now = 46, sum = 1035 +the 1th child : now = 47, sum = 1081 +the 2th child : now = 48, sum = 1128 +the 2th child : now = 49, sum = 1176 +the 1th child : now = 50, sum = 1225 +the 2th child : now = 51, sum = 1275 +the 1th child : now = 52, sum = 1326 +the 2th child : now = 53, sum = 1378 +the 1th child : now = 54, sum = 1431 +the 7th child : now = 55, sum = 1485 +the 7th child : now = 56, sum = 1540 +the 7th child : now = 57, sum = 1596 +the 1th child : now = 58, sum = 1653 +the 6th child : now = 59, sum = 1711 +the 1th child : now = 60, sum = 1770 +the 2th child : now = 61, sum = 1830 +the 1th child : now = 62, sum = 1891 +the 1th child : now = 63, sum = 1953 +the 1th child : now = 64, sum = 2016 +the 4th child : now = 65, sum = 2080 +the 1th child : now = 66, sum = 2145 +the 4th child : now = 67, sum = 2211 +the 1th child : now = 68, sum = 2278 +the 4th child : now = 69, sum = 2346 +the 1th child : now = 70, sum = 2415 +the 4th child : now = 71, sum = 2485 +the 1th child : now = 72, sum = 2556 +the 4th child : now = 73, sum = 2628 +the 1th child : now = 74, sum = 2701 +the 4th child : now = 75, sum = 2775 +the 4th child : now = 76, sum = 2850 +the 1th child : now = 77, sum = 2926 +the 4th child : now = 78, sum = 3003 +the 1th child : now = 79, sum = 3081 +the 3th child : now = 80, sum = 3160 +the 1th child : now = 81, sum = 3240 +the 2th child : now = 82, sum = 3321 +the 1th child : now = 83, sum = 3403 +the 1th child : now = 84, sum = 3486 +the 6th child : now = 85, sum = 3570 +the 6th child : now = 86, sum = 3655 +the 4th child : now = 87, sum = 3741 +the 3th child : now = 88, sum = 3828 +the 1th child : now = 89, sum = 3916 +the 2th child : now = 90, sum = 4005 +the 1th child : now = 91, sum = 4095 +the 2th child : now = 92, sum = 4186 +the 1th child : now = 93, sum = 4278 +the 3th child : now = 94, sum = 4371 +the 4th child : now = 95, sum = 4465 +the 6th child : now = 96, sum = 4560 +the 6th child : now = 97, sum = 4656 +the 6th child : now = 98, sum = 4753 +the 4th child : now = 99, sum = 4851 +the 6th child : now = 100, sum = 4950 +the 1th child : now = 101, sum = 5050 +sum = 5050 +sum = 5050 +sum = 5050 +sum = 5050 +sum = 5050 +sum = 5050 +sum = 5050 +sum = 5050 +sum = 5050 +sum = 5050 +sum = 5050 +``` + +flock + + + + + + + + + + + + + +# 1.作业2 + +``` +命令./test -m "msg" +结果输出:msg +命令./test +vim a.txt +input msg +printf msg +delete a.txt +模拟过程,可能需要3个进程 +``` + + + +ls,ls -al, + +分列显示,有排序 + +显示/不显示隐藏文件, + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +# 0.end \ No newline at end of file diff --git "a/02.c++\347\254\224\350\256\260/07.\344\271\240\351\242\230\347\255\224\347\226\221.md" "b/02.c++\347\254\224\350\256\260/07.\344\271\240\351\242\230\347\255\224\347\226\221.md" new file mode 100644 index 0000000..6765625 --- /dev/null +++ "b/02.c++\347\254\224\350\256\260/07.\344\271\240\351\242\230\347\255\224\347\226\221.md" @@ -0,0 +1,3709 @@ +\--- + +title: 习题答疑 + +date: 2020-12-29 08:08:15 + +tags: 习题答疑 + +categories: 习题答疑 + +\--- + +# 9.课程答疑(一) + +## 1.逆波兰式 + +![截屏2021-01-31 上午11.29.22](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-01-31%20%E4%B8%8A%E5%8D%8811.29.22.png) + + + + + +1. 表达式树,最有价值的是当成思维逻辑结构中的数据结构 + +2. 表达式求值程序,本质上就是对表达式树的递归遍历 + +3. 重点观察表达式树的特性:根节点是整个表达式中优先级最低的运算符,也就是最后一个被计算的运算符 + + + + **表达式求解程序** + + 1. **Step1:**找到表达式中优先级最低的运算符的位置 + 2. **Step2:**递归计算运算符左右两侧表达式的值 + 3. **Step3:**根据当前运算符获得运算结果 + + + + **输出逆波兰式** + + 1. 表达式求解程序,本质上是对表达树的遍历 + 2. 所以,采用后序遍历的方式,输出逆波兰式的结果 + + + + + +```cpp +#include +#include +using namespace std; + +#define INF 0x3f3f3f3f + +int calc(char *str, int l, int r) { + int pos = -1, pri = INF - 1; + for (int i = l; i <= r; i++) { + int cur = INF; + switch (str[i]) { + case '+': + case '-': cur = 1; break; + case '*': + case '/': cur = 2; break; + } + if (cur <= pri) { + pos = i, pri = cur; + } + } + if (pos == -1) {//表达式中已经没有运算符,全部都是数字 + int num = 0; + for (int i = l; i <= r; i++) { + if (str[i] < '0' || str[i] > '9') continue; + num = num * 10 + str[i] - '0'; + } + return num; + } + //pos点为优先级最低点,分别计算左右子树的值 + int a = calc(str, l, pos - 1); + int b = calc(str, pos + 1, r); + switch (str[pos]) { + case '+' : return a + b; + case '-' : return a - b; + case '*' : return a * b; + case '/' : return a / b; + } + return 0; +} + +char str[10000]; + +int main() { + scanf("%s", str); + printf("%d\n", calc(str, 0, strlen(str) - 1)); + + + return 0; +} + +``` + + + +```cpp +#include +#include +#include +using namespace std; + +#define INF 0x3f3f3f3f + +int calc(char *str, int l, int r) { + int pos = -1, pri = INF - 1; + int temp = 0;//带括号 + for (int i = l; i <= r; i++) { + int cur = INF; + switch (str[i]) { + case '(': temp += 100; break; + case ')': temp -= 100; break; + case '+': + case '-': cur = temp + 1; break; + case '*': + case '/': cur = temp + 2; break; + case '^': cur = temp + 3; break; + } + if (cur <= pri) { + pos = i, pri = cur; + } + } + if (pos == -1) {//表达式中已经没有运算符,全部都是数字 + int num = 0; + for (int i = l; i <= r; i++) { + if (str[i] < '0' || str[i] > '9') continue; + num = num * 10 + str[i] - '0'; + } + return num; + } + //pos点为优先级最低点,分别计算左右子树的值 + int a = calc(str, l, pos - 1); + int b = calc(str, pos + 1, r); + switch (str[pos]) { + case '+' : return a + b; + case '-' : return a - b; + case '*' : return a * b; + case '/' : return a / b; + case '^' : return (int)pow(a, b); + } + return 0; +} + +char str[10000]; + +int main() { + while (~scanf("%[^\n]s", str)) { + getchar(); + printf("%s = %d\n", str, calc(str, 0, strlen(str) - 1)); + } + + return 0; +} +``` + + + +``` +(3 + 5) * (10 + 40) = 400 +(2 + 3)^2 = 25 +``` + + + +带^和() + +```cpp +#include +#include +#include +using namespace std; + +#define INF 0x3f3f3f3f + +int calc(char *str, int l, int r) { + int pos = -1, pri = INF - 1; + int temp = 0;//带括号 + for (int i = l; i <= r; i++) { + int cur = INF; + switch (str[i]) { + case '(': temp += 100; break; + case ')': temp -= 100; break; + case '+': + case '-': cur = temp + 1; break; + case '*': + case '/': cur = temp + 2; break; + case '^': cur = temp + 3; break; + } + if (cur <= pri) { + pos = i, pri = cur; + } + } + if (pos == -1) {//表达式中已经没有运算符,全部都是数字 + int num = 0; + for (int i = l; i <= r; i++) { + if (str[i] < '0' || str[i] > '9') continue; + num = num * 10 + str[i] - '0'; + } + printf("%d", num); + return num; + } + //pos点为优先级最低点,分别计算左右子树的值 + int a = calc(str, l, pos - 1); + printf(" "); + int b = calc(str, pos + 1, r); + printf(" %c", str[pos]); + switch (str[pos]) { + case '+' : return a + b; + case '-' : return a - b; + case '*' : return a * b; + case '/' : return a / b; + case '^' : return (int)pow(a, b); + } + return 0; +} + +char str[10000]; + +int main() { + while (~scanf("%[^\n]s", str)) { + getchar(); + int val = calc(str, 0, strlen(str) - 1); + printf("\n"); + printf("%s = %d\n", str, val); + } + + return 0; +} +``` + + + +``` +3 5 + 10 40 + * +(3 + 5) * (10 + 40) = 400 +2 3 + 2 ^ +(2 + 3)^2 = 25 +``` + + + + + +## 2.根据三元组数列建立二叉链表 + +![截屏2021-01-31 下午4.42.10](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-01-31%20%E4%B8%8B%E5%8D%884.42.10.png) + +![截屏2021-01-31 下午4.42.10](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-01-31%20%E4%B8%8B%E5%8D%884.42.21.png) + + + +1. 本质就是一个模拟题 +2. 模拟题的学习重点,就是学习各种各样的模拟技巧,以及对应的编码技巧 +3. **技巧1:**用一个数组,记录字符所对应的节点地址 + + + +```cpp +#include +using namespace std; + +typedef struct Node { + char ch; + struct Node *lchild, *rchild; +} Node; + +Node *arr[26]; + +Node *getNewNode(char ch) { + Node *p = (Node *)malloc(sizeof(Node)); + p->ch = ch; + p->lchild = p->rchild = NULL; + return p; +} + +char str[10]; +void output(Node *root) { + if(root == NULL) return ; + printf("%c", root->ch); + if (root->lchild == NULL && root->rchild == NULL) return ; + printf("("); + output(root->lchild); + if (root->rchild) { + printf(","); + output(root->rchild); + } + printf(")"); + return ; +} + +int main() { + Node *root = NULL; + Node *p; + while (scanf("%s", str)) {//读入三元组 + if (str[0] == '^' && str[1] == '^') break;//已经读入到结尾 + p = getNewNode(str[1]);//当前节点 + arr[str[1] - 'A'] = p; + if (str[0] == '^') { + root = p;//根节点 + continue; + } + //判断第三个字母 + switch (str[2]) { + case 'L': arr[str[0] - 'A']->lchild = p; break; + case 'R': arr[str[0] - 'A']->rchild = p; break; + } + } + output(root); + printf("\n"); + + return 0; +} +``` + + + + + + + + + +## 3.植物大战僵尸 + +![截屏2021-01-31 下午5.07.05](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-01-31%20%E4%B8%8B%E5%8D%885.07.05.png) + + + +![s](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-01-31%20%E4%B8%8B%E5%8D%885.07.23.png) + + + + + +1. **题目建模:**每一轮,找到一个集合中的最大值,并且删掉这个最大值。所以,尝试用堆解决。 +2. **题目难点:**僵尸之间的相对顺序,时刻发生改变,而堆中,数据是不变的。 +3. **问题简化:**如果僵尸之间的相对顺序不变,这道题目,就可以用堆求解,甚至是简单的排序即可。 +4. **发现突破口:**当两个僵尸速度相同时,两个僵尸之间的相对位置就不会发生改变。 +5. 题目中,速度都是整数,且在1到100之间,也就是说,最多存在 100 种速度。 +6. 根据速度,建立 100 个堆,将速度相同的僵尸,塞到一个堆中 +7. 每一轮,在100个堆顶元素中,找到一个跑在最前面的僵尸,干掉即可 + + + +**Bug-1:**没有严格按照题目要求实现程序,没有考虑到位置相同的情况。 + +**Bug-2:**写程序中的小笔误,在堆的 pop 操作中,参与比较的两个元素的下标错误。 + + + + + +```cpp +#include +using namespace std; + +#define swap(a, b) {\ + __typeof(a) c = a;\ + a = b, b = c;\ +} +#define MAX_N 50000 + +typedef struct Data { + int n, f, s;//n代表编号,总数量,有n个僵尸,第一秒的速度为f,之后的速度为s +} Data; + +Data heap[101][MAX_N + 5];//堆 + +int gt(Data a, Data b) { + if (a.f - b.f) return a.f > b.f; + return a.n < b.n; +} + +void push(Data *h, Data val) { + h[++h[0].n] = val; + int ind = h[0].n; + while (ind / 2 && gt(h[ind], h[ind / 2])) { + swap(h[ind], h[ind / 2]); + ind /= 2; + } + return ; +} + +void pop(Data *h) { + swap(h[1], h[h[0].n]); + h[0].n -= 1; + int ind = 1, temp; + while (ind * 2 <= h[0].n) { + temp = ind; + if (gt(h[ind * 2], h[temp])) temp = ind * 2; + if (ind * 2 + 1 <= h[0].n && gt(h[ind * 2 + 1], h[temp])) temp = ind * 2 + 1; + if (temp == ind) break;//向下调整到了合适的位置 + swap(h[temp], h[ind]); + ind = temp; + } +} + +int empty(Data *h) { return h[0].n == 0; } +Data top(Data *h) { return h[1]; } +void clear(Data *h) { h[0].n = 0; } + +void init_heap() {//清空堆 + for (int i = 0; i <= 100; i++) clear(heap[i]); + return ; +} + +void solve() { + init_heap(); + int n, f, s; + scanf("%d", &n); + for (int i = 1; i <= n; i++) { + scanf("%d%d", &f, &s); + Data d = {i, f, s}; + push(heap[s], d);//插入堆中 + } + for (int i = 1; i <= n; i++) {//干掉n个僵尸 + int ind = 0, pos = 0; + for (int j = 1; j <= 100; j++) {//找到跑在最前面的僵尸 + if (empty(heap[j])) continue; + int cur_pos = (i - 1) * j + top(heap[j]).f; + if (ind == 0) { + ind = j, pos = cur_pos; + continue; + } + if (pos < cur_pos || (pos == cur_pos && top(heap[j]).n < top(heap[ind]).n)) { + ind = j, pos = cur_pos; + } + } + if (i - 1) printf(" "); + //printf("%d %d %d %d\n", top(heap[ind]).n, top(heap[ind]).f, ind, pos); + printf("%d", top(heap[ind]).n);//干掉僵尸 + pop(heap[ind]);// + } + printf("\n"); + return ; +} + + +int main() { + int tcase, n = 0; + scanf("%d", &tcase); + while ((n++) < tcase) { + printf("Case #%d:\n", n); + solve(); + } + + return 0; +} +``` + + + +# 10.课程答疑(二)1 + + + +## 一、预习资料-【数据结构】-最短路简化 + +1. 广搜裸题 + +``` + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +using namespace std; + +#define MAX_N 1000 +int g[MAX_N + 5][MAX_N + 5]; +inline void add(int a, int b) { + g[a][++g[a][0]] = b; + g[b][++g[b][0]] = a; + return ; +} +int ans[MAX_N + 5]; + +int main() { + int n, m, c; + cin >> n >> m >> c; + for (int i = 0, a, b; i < m; i++) { + cin >> a >> b; + add(a, b); + } + queue q; + ans[c] = 1; + q.push(c); + while (!q.empty()) { + int ind = q.front(); + for (int i = 1; i <= g[ind][0]; i++) { + int to = g[ind][i]; + if (ans[to]) continue; + ans[to] = ans[ind] + 1; + q.push(to); + } + q.pop(); + } + for (int i = 1; i <= n; i++) { + cout << ans[i] - 1 << endl; + } + return 0; +} + +``` + + + +## 二、预习资料-【数据结构】-游戏分组 + +1. 并查集裸题 +2. 并查集的简化实现方式 +3. **路径压缩:**代码实现短,优化效果明显 +4. **按秩优化:**代码实现稍复杂,对于随机数据的优化效果一般,还需要占用额外的存储空间 +5. 在快速编码的场景中,只需要实现路径压缩即可 + +``` + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +using namespace std; + +#define MAX_N 1000000 +int fa[MAX_N + 5]; +int get(int x) { + return fa[x] = (x == fa[x] ? x : get(fa[x])); +} +void merge(int a, int b) { + fa[get(a)] = get(b); + return ; +} + +int main() { + int n, m; + cin >> n >> m; + for (int i = 0; i < n; i++) fa[i] = i; + for (int i = 0, a, b; i < m; i++) { + cin >> a >> b; + merge(a, b); + } + int ans = 0; + for (int i = 0; i < n; i++) { + ans += (fa[i] == i); + } + cout << ans << endl; + return 0; +} + +``` + + + +## 三、预习资料-【数据结构】-字符串旋转矩阵 + +1. 首要解决的问题:还原原字符串 +2. **Step1:**通过最后一列的字符,确定相应的第一列的字符 +3. **Step2:**得出结论,相同字符的相对顺序在第一列和最后一列中相同 +4. **Step3:**确定了每一个字符的编号,以及编号与编号之间的前后关系,即可还原原字符串 +5. 由于反复标记 fail 指针链上的状态,导致时间超限,在这里有一个特殊的程序优化 + +![image-20210124215823630](../guanghu/Library/Application%2520Support/typora-user-images/image-20210124215823630.png) + +``` + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +using namespace std; + +#define MAX_N 110000 +char t[MAX_N + 5], s[MAX_N + 5]; +int ind[MAX_N + 5], *ans[MAX_N + 5]; + +bool cmp(int i, int j) { + if (t[i] - t[j]) return t[i] < t[j]; + return i < j; +} + +void convert(char *t, char *s) { + int n = 0; + for (n = 0; t[n]; n++) ind[n] = n; + sort(ind, ind + n, cmp); + for (int i = 0, p = ind[0]; i < n; i++, p = ind[p]) { + s[i] = t[p]; + } + s[n] = 0; + return ; +} + +struct Node { + int flag, *ans; + int next[26], fail; +} tree[2000005]; +int que[2000005], head, tail; +int root = 1, cnt = 2; +inline int getNewNode() { return cnt++; } +int *insert(char *s) { + int p = root; + for (int i = 0; s[i]; i++) { + int ind = s[i] - 'a'; + if (tree[p].next[ind] == 0) tree[p].next[ind] = getNewNode(); + p = tree[p].next[ind]; + } + tree[p].flag = 1; + if (tree[p].ans == NULL) { + tree[p].ans = new int(0); + } + return tree[p].ans; +} + +void build() { + head = tail = 0; + tree[root].fail = 0; + for (int i = 0; i < 26; i++) { + if (tree[root].next[i] == 0) { + tree[root].next[i] = root; + continue; + } + tree[tree[root].next[i]].fail = root; + que[tail++] = tree[root].next[i]; + } + while (head < tail) { + int p = que[head++]; + for (int i = 0; i < 26; i++) { + int c = tree[p].next[i], k = tree[p].fail; + if (c == 0) { + tree[p].next[i] = tree[k].next[i]; + continue; + } + tree[c].fail = tree[k].next[i]; + que[tail++] = c; + } + } + return ; +} + +void match(char *s) { + for (int i = 0, p = tree[root].next[s[0] - 'a'], q, k; s[i]; i++, p = tree[p].next[s[i] - 'a']) { + q = p; + while (q) { + if (tree[q].flag) { + *tree[q].ans += 1; + } + k = q; + q = tree[q].fail; + tree[k].fail = 0; + } + } + return ; +} + +void init() { + cnt = 2; + memset(tree, 0, sizeof(tree)); + memset(ans, 0, sizeof(ans)); + return ; +} + +int solve(char *t) { + init(); + convert(t, s); + int n; + cin >> n; + for (int i = 0; i < n; i++) { + cin >> t; + ans[i] = insert(t); + } + build(); + match(s + 1); + for (int i = 0; i < n; i++) { + cout << (ans[i][0] ? "YES" : "NO") << endl; + } + return 0; +} + +int main() { + while (cin >> t) solve(t); + return 0; +} + +``` + + + +## 四、预习资料-【数据结构】-灌溉 + +### 最小生成树问题-Kruskal 算法 + +1. Step1:对于所有边从小到大排序 +2. Step2:依次取出每一条边,试着加入图中,边上的两个点不联通的情况下,才将边加入图中 + + + +除了学习一个新算法以外,这道题目,一无是处。 + +![image-20210124215840760](../guanghu/Library/Application%2520Support/typora-user-images/image-20210124215840760.png) + +``` + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +using namespace std; + +#define MAX_N 10000 +struct Edge { + int v, u, c; +}; +vector e; + +bool cmp(const Edge &a, const Edge &b) { + return a.c < b.c; +} + +int fa[MAX_N + 5]; +int get(int x) { + return fa[x] = (x - fa[x] ? get(fa[x]) : x); +} +void merge(int a, int b) { + fa[get(a)] = get(b); +} +void init(int n) { + for (int i = 0; i <= n; i++) fa[i] = i; +} + +int main() { + int n; + cin >> n; + for (int i = 1, a; i <= n; i++) { + for (int j = 1; j <= n; j++) { + cin >> a; + if (i < j) e.push_back({i, j, a}); + } + } + sort(e.begin(), e.end(), cmp); + init(n); + int ans = 0; + for (int i = 0; i < e.size(); i++) { + int v = e[i].v, u = e[i].u, c = e[i].c; + if (get(v) == get(u)) continue; + merge(v, u); + ans += c; + } + cout << ans << endl; + return 0; +} + +``` + + + +## 五、Leetcode-05-最长回文子串 + +### Manacher 算法 + +1. Step1:对原字符串进行特殊处理,在每两个字符中间加入 # 字符 +2. Step2:依次求解每一个位置的最大回文半径 +3. 在求解过程中,记录一个 c 点,$c+r[c]$ 最大 +4. 求回文半径时:要不然借助原有信息,要不然就是暴力匹配,暴力匹配过程,均摊时间复杂度 O(1) +5. **时间复杂度:**O(n) + +![image-20210124215852853](../guanghu/Library/Application%2520Support/typora-user-images/image-20210124215852853.png) + + + +![image-20210124215900772](../guanghu/Library/Application%2520Support/typora-user-images/image-20210124215900772.png) + + + +![image-20210124215910516](../guanghu/Library/Application%2520Support/typora-user-images/image-20210124215910516.png) + +``` + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +using namespace std; + +class Solution { +public: + string get_new_string(string &s) { + string ns = "#"; + for (int i = 0; s[i]; i++) { + (ns += s[i]) += "#"; + } + return ns; + } + string longestPalindrome(string s) { + string ns = get_new_string(s); + int *r = new int[ns.size()], c; + r[0] = 1, c = 0; + for (int i = 1; i < ns.size(); i++) { + if (i >= c + r[c]) { + r[i] = 1; + } else { + r[i] = min(c + r[c] - i, r[2 * c - i]); + } + while (i - r[i] >= 0 && ns[i - r[i]] == ns[i + r[i]]) r[i] += 1; + if (i + r[i] > c + r[c]) c = i; + } + int ans = 0; + string ret = ""; + for (int i = 0; ns[i]; i++) { + if (r[i] <= ans) continue; + ans = r[i]; + ret = ""; + for (int j = i - r[i] + 1; j < i + r[i]; j++) { + if (ns[j] == '#') continue; + ret += ns[j]; + } + } + return ret; + } +}; + +int main() { + Solution solv; + string s; + while (cin >> s) { + cout << solv.longestPalindrome(s) << endl; + } + return 0; +} + +``` + + + +# 11.课程答疑(三) + + + +## 一、【中等】leetcode-146-LRU缓存机制 + +[leetcode-146-LRU缓存机制](https://leetcode-cn.com/problems/lru-cache/) + +![截屏2021-02-01 上午11.20.38](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-01%20%E4%B8%8A%E5%8D%8811.20.38.png) + +![截屏2021-02-01 上午11.20.47](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-01%20%E4%B8%8A%E5%8D%8811.20.47.png) + +1. 哈希表 + 链表解决 $O(1)$ 读取,以及 $O(1)$ 修改缓存数据节点位置的操作 +2. **弯路1:**一开始想到了哈希表,但忽略了利用链表实现 $O(1)$ 修改 +3. **弯路2:**由于头尾指针会发生变化,忽略了虚拟节点的处理技巧 + + + +代码演示 + +``` + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +using namespace std; + +class LRUCache { +public: + class Node { + public : + Node() : Node(0, 0) {} + Node(int key, int value) : pre(nullptr), next(nullptr), key(key), value(value) {} + Node *pre, *next; + int key, value; + }; + int capacity, node_cnt; + unordered_map ind; + Node head, tail; + LRUCache(int capacity) { + this->capacity = capacity; + this->node_cnt = 0; + head.next = &tail; + tail.pre = &head; + } + int get(int key) { + if (ind.find(key) == ind.end()) return -1; + Node *p = ind[key]; + remove_node(p); + insert_tail(p); + return p->value; + } + void output() { + Node *p = head.next; + while (p) { + cout << "(" << p->key << ", " << p->value << ")->"; + p = p->next; + } + cout << endl; + return ; + } + void remove_node(Node *p) { + p->next->pre = p->pre; + p->pre->next = p->next; + return ; + } + void insert_tail(Node *p) { + p->next = &tail; + p->pre = tail.pre; + tail.pre = p; + return ; + } + void put(int key, int value) { + Node *p; + if (ind.find(key) == ind.end()) { + p = new Node(key, value); + node_cnt += 1; + insert_tail(p); + ind[key] = p; + } else { + get(key); + } + if (node_cnt > capacity) { + p = head.next; + remove_node(p); + delete p; + node_cnt -= 1; + } + return ; + } +}; + +/** + * Your LRUCache object will be instantiated and called as such: + * LRUCache* obj = new LRUCache(capacity); + * int param_1 = obj->get(key); + * obj->put(key,value); + */ + +``` + + + +## 二、【困难】Leetcode-460-LFU缓存 + +1. 编码较复杂,基于十字链表实现 LFU 缓存机制 +2. 将出现次数相同的节点,存储在同一个 LRUCache 中 +3. 将所有非空的 LRUCache ,按照代表的次数,链接成一个大链表 +4. 删除节点的操作,简化成了:删除第一个 LRUCache 中的头结点 +5. **操作1:**删除一个节点 +6. **操作2:**新增一个节点 +7. **操作3:**将一个现有节点,移动到下一个 LRUCache 中 + +``` + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +using namespace std; + +class Node { +public : + Node() : Node(0, 0) {} + Node(int key, int value) + : pre(nullptr), next(nullptr), + key(key), value(value), cnt(0) {} + + Node *pre, *next; + int key, value, cnt; +}; + +class LRUCache { +public: + int node_cnt; + Node head, tail; + LRUCache *pre, *next; + LRUCache() : pre(nullptr), next(nullptr) { + this->node_cnt = 0; + head.next = &tail; + tail.pre = &head; + } + void output() { + return ; + Node *p = head.next; + while (p) { + cout << "(" << p->key << ", " << p->value << ")->"; + p = p->next; + } + cout << endl; + p = tail.pre; + while (p) { + cout << "(" << p->key << ", " << p->value << ")->"; + p = p->pre; + } + cout << endl; + return ; + } + void remove_node(Node *p) { + p->next->pre = p->pre; + p->pre->next = p->next; + node_cnt -= 1; + return ; + } + void insert_tail(Node *p) { + p->next = &tail; + p->pre = tail.pre; + tail.pre->next = p; + tail.pre = p; + node_cnt += 1; + return ; + } + void put(Node *p) { + insert_tail(p); + return ; + } + Node *pop() { + Node *p = head.next; + head.next = p->next; + node_cnt -= 1; + return p; + } +}; + +class LFUCache { +public: + unordered_map lfu; + unordered_map ind; + LRUCache head, tail; + int capacity, node_cnt; + LFUCache(int capacity) : capacity(capacity), node_cnt(0) { + head.next = &tail; + tail.pre = &head; + } + void remove_from_LRUCache(Node *p) { + LRUCache *c1 = lfu[p->cnt]; + c1->remove_node(p); + if (c1->node_cnt == 0) { + remove_LRUCache(c1); + lfu.erase(lfu.find(p->cnt)); + delete(c1); + } + node_cnt -= 1; + return ; + } + void insert_to_LRUCache(Node *p) { + LRUCache *c = lfu[p->cnt]; + c->put(p); + node_cnt += 1; + return ; + } + void remove_LRUCache(LRUCache *c) { + c->next->pre = c->pre; + c->pre->next = c->next; + return ; + } + void insert_LRUCache(LRUCache *c1, LRUCache *c2) { + c2->pre = c1, c2->next = c1->next; + c1->next->pre = c2, c1->next = c2; + return ; + } + void move_next_LRUCache(Node *p) { + LRUCache *c1 = lfu[p->cnt], *c2; + if (lfu.find(p->cnt + 1) == lfu.end()) { + c2 = new LRUCache(); + insert_LRUCache(c1, c2); + lfu[p->cnt + 1] = c2; + } + remove_from_LRUCache(p); + p->cnt += 1; + insert_to_LRUCache(p); + return ; + } + + int get(int key) { + if (ind.find(key) == ind.end()) return -1; + Node *p = ind[key]; + move_next_LRUCache(p); + return p->value; + } + + void put(int key, int value) { + Node *p; + if (ind.find(key) == ind.end()) { + if (node_cnt == capacity) { + remove_from_LRUCache(head.next->head.next); + } + p = new Node(key, value); + if (lfu.find(p->cnt) == lfu.end()) { + lfu[p->cnt] = new LRUCache(); + insert_LRUCache(&head, lfu[p->cnt]); + } + insert_to_LRUCache(p); + } else { + move_next_LRUCache(ind[key]); + ind[key]->value = value; + } + return ; + } +}; + +int main() { + + + + return 0; +} + +``` + + + + + +# 12.习题答疑课(四) + +## 一、HZOJ-328-楼兰图腾 + +![截屏2021-02-01 下午5.02.59](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-01%20%E4%B8%8B%E5%8D%885.02.59.png) + +![截屏2021-02-01 下午5.03.08](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-01%20%E4%B8%8B%E5%8D%885.03.08.png) + + + +```cpp +/************************************************************************* + > File Name: 2.HZOJ328-60.cpp + > Author: huguang + > Mail: hug@haizeix.com + > Created Time: + ************************************************************************/ + +#include +#include +using namespace std; +#define max_n 200000 +#define lowbit(x) ((x) & (-x)) +int num[max_n + 5]; + +void add(int j, int v, int n) { + while (j<=n) { + num[j] += v; + j += lowbit(j); + } + return ; +} +int alg(int j) { + int sum = 0; + while (j) { + sum += num[j]; + j -= lowbit(j); + } + return sum; +} +int main() { + long long n,a,x = 0, y = 0; + cin>>n; + for (int i = 1; i <= n; i++) { + cin >> a; + add(a + 1, 1, n); + long long q = alg(a); + long long w = i - 1 - q; + long long e = n - a - w; + long long r = a - 1 - q; + x += w * e; + y += q * r; + } + cout << x << " " << y; + return 0; +} + +``` + + + + + + + +## 二、HZOJ-224-复合线段树 + +![截屏2021-02-01 下午5.18.34](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-01%20%E4%B8%8B%E5%8D%885.18.34.png) + +![截屏2021-02-01 下午5.18.43](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-01%20%E4%B8%8B%E5%8D%885.18.43.png) + +### 1、解题思路 + +1. 因为要支持区间操作,所以要用到线段树的懒标记技巧 +2. 有两种区间修改操作,乘法与加法 +3. $a\times(s + b) = a \times s + a \times b$,可知乘法操作的时候,需要同时修改区间和值与加法懒标记 +4. 加法懒标记只需要单独修改加法懒标记即可 +5. 懒标记更新技巧:先下沉乘法懒标记,再下沉加法懒标记 + +![image-20210130215543216](../guanghu/Library/Application%2520Support/typora-user-images/image-20210130215543216.png) + +### 2、0分程序改 BUG + +1. 主要 BUG,超过内存限制 +2. 原因分析:程序中,将懒标记改成了记录具体的操作序列,所以使得线段树节点空间激增,超过内存限制 +3. 在不修改懒标记技巧的情况下,无法满分通过程序 +4. **思考:**如果把线段树的节点修改成动态申请的,说不定会通过空间限制。 + + + +### 3、10分程序改 BUG + +1. 懒标记更新方式严重错误,源程序中将懒标记更新到了整棵子树中。 +2. **修改1:**懒标记从父节点,只下沉到直接子节点处 +3. **修改2:**标记下沉时,要先将乘法标记下沉,再将加法标记下沉 +4. **修改3:**该 $\%p$ 的地方就 $\%p$ + + + +### 4、50分程序改 BUG + +1. 下沉乘法懒标记时出错,应该同时修改加法懒标记 +2. **修改1:**将乘法标记下沉与加法标记下沉分成两个函数分别实现 +3. **修改2:**将数据类型改成 $long\ long$ + + + + + +```cpp + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +using namespace std; + +#define MAX_N 100000 +struct Node { + long long sum, t1, t2; +} tree[(MAX_N << 2) + 5]; +long long a[MAX_N + 5]; +long long n, m, p; + +void mul_tag(long long ind, long long x) { + tree[ind].sum *= x; + tree[ind].sum %= p; + tree[ind].t1 *= x; + tree[ind].t1 %= p; + tree[ind].t2 *= x; + tree[ind].t2 %= p; + return ; +} + +void add_tag(long long ind, long long x, long long n) { + tree[ind].sum += x * n; + tree[ind].sum %= p; + tree[ind].t2 += x; + tree[ind].t2 %= p; + return ; +} + +void UP(long long ind) { + tree[ind].sum = tree[ind << 1].sum + tree[ind << 1 | 1].sum; + tree[ind].sum %= p; + return ; +} + +void DOWN(long long ind, long long l, long long r) { + if (tree[ind].t1 - 1 || tree[ind].t2) { + long long a = tree[ind].t1, b = tree[ind].t2; + long long mid = (l + r) >> 1; + mul_tag(ind << 1, a); + mul_tag(ind << 1 | 1, a); + add_tag(ind << 1, b, mid - l + 1); + add_tag(ind << 1 | 1, b, r - mid); + tree[ind].t1 = 1; + tree[ind].t2 = 0; + } + return ; +} + +void build(long long ind, long long l, long long r) { + tree[ind].t1 = 1; tree[ind].t2 = 0; + if (l == r) { + tree[ind].sum = a[l]; + return ; + } + long long mid = (l + r) >> 1; + build(ind << 1, l, mid); + build(ind << 1 | 1, mid + 1, r); + UP(ind); + return ; +} + +void modify(long long ind, long long flag, long long x, long long y, long long val, long long l, long long r) { + if (x <= l && r <= y) { + if (flag == 0) { + mul_tag(ind, val); + } else { + add_tag(ind, val, r - l + 1); + } + return ; + } + long long mid = (l + r) >> 1; + DOWN(ind, l, r); + if (x <= mid) modify(ind << 1, flag, x, y, val, l, mid); + if (y > mid) modify(ind << 1 | 1, flag, x, y, val, mid + 1, r); + UP(ind); + return ; +} + +long long query(long long ind, long long x, long long y, long long l, long long r) { + if (x <= l && r <= y) { + return tree[ind].sum; + } + long long mid = (l + r) >> 1; + long long ans = 0; + DOWN(ind, l, r); + if (x <= mid) ans += query(ind << 1, x, y, l, mid); + ans %= p; + if (y > mid) ans += query(ind << 1 | 1, x, y, mid + 1, r); + ans %= p; + UP(ind); + return ans; +} + +int main() { + cin >> n >> m >> p; + for (long long i = 1; i <= n; i++) cin >> a[i]; + build(1, 1, n); + long long op, x, y, k; + for (long long i = 0; i < m; i++) { + cin >> op >> x >> y; + switch (op) { + case 1: + case 2: { + cin >> k; + modify(1, op - 1, x, y, k, 1, n); + } break; + case 3: { + cout << query(1, x, y, 1, n) << endl; + } break; + } + } + return 0; +} + +``` + + + + + + + + + + + +## 三、HZOJ-52-古老的打字机 + +### 1、40分程序改 BUG + +1. 状态转移过程,采用了最朴素的状态转移方式,时间效率极差 +2. 没有采用斜率优化的转移技巧 + + + +### 2、40分程序改 BUG + +1. 程序过程中需要求前缀和,值范围超过了整型表示范围,将 $int$ 改成 $long\ long$ 即可 + + + + + + + +# 13.习题答疑课(五) + +## 一、【0分】HZOJ-64-海贼红黑树 + +1. 做了错误的假设,假设第一个操作是插入节点的操作 +2. 删除了$\_\_attribute\_\_((constructor))$,导致 NIL节点没有被初始化 +3. 没有修改双黑节点的颜色,将双黑变成普通黑 + + + +## 二、剑指 Offer-56-数字出现的次数Ⅱ + +1. 写出真值表,推导状态转化公式 + +![image-20210131215620980](../guanghu/Library/Application%2520Support/typora-user-images/image-20210131215620980.png) + + + +**扩展问题:**只有一个数字出现次数不足 4 次,其他数字,都出现了 4 次,找到这个数字 + +``` + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +using namespace std; + +class Solution { +public: + int singleNumber(vector& nums) { + int a = 0, b = 0, a1, b1; + for (int i = 0, c; i < nums.size(); i++) { + c = nums[i]; + a1 = (a & ~b & ~c) | (~a & b & c); + b1 = (~a & b & ~c) | (~a & ~b & c); + a = a1; + b = b1; + } + return b; + } +}; + +int main() { + + + return 0; +} + +``` + + + +## 三、HZOJ-646-海贼王堆 + +1. 堆的裸题 +2. 使用 set 模拟堆的行为 + + + +``` + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +using namespace std; + +struct Data { + string name; + int sex, age, ind; + bool operator<(const Data &a) const { + if (name.find("wang") == 0 && a.name.find("wang") != 0) return true; + if (name.find("wang") != 0 && a.name.find("wang") == 0) return false; + if (sex - a.sex) return sex == 0; + if (age - a.age) return age > a.age; + if (name != a.name) return name < a.name; + return ind < a.ind; + } +}; + +int main() { + set s; + int n, op; + cin >> n; + for (int i = 0; i < n; i++) { + cin >> op; + switch (op) { + case 1: { + string name; + int sex, age; + cin >> name >> sex >> age; + s.insert({name, sex, age, i}); + } break; + case 2: { + if (s.size() == 0) cout << "empty" << endl; + else cout << s.begin()->name << endl; + } break; + case 3: { + if (s.size() == 0) break; + s.erase(s.begin()); + } break; + } + } + return 0; +} + +``` + +## 四、面试题反馈 + +### 字典树面试题1 + +**问题描述:**例如一个用户输入了一个字符串:hellu,那它是不存在于字典中的,那么我们怎么判断出来呢? + +**要求:** + +(1)定义存储所有正确单词的数据结构。 + +(2)写一个录入单词的函数。 + +(3)写一个判断用户输入的单词是否正确的函数。 + +**注:**忽略大小写,可以认为都是小写。 + + + +### 字典树面试题2 + +**问题描述:**出错的情况下,提示出正确的单词(可能有多个)。出错的情况很多,前中后的位置都有可能出错,可能错一个字母,也可能多一个或少一个字母。 + +**要求:** + +(1)找出跟错误单词相差一个字母的正确单词即可。 + +(2)定义存储单词的数据结构。 + +(3)写出查询函数,返回n个正确的单词。 + + + + + + + + + + + + + +# 00000000 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +# 0000000000 + + + diff --git "a/02.c++\347\254\224\350\256\260/08.C++\347\254\224\350\256\260.md" "b/02.c++\347\254\224\350\256\260/08.C++\347\254\224\350\256\260.md" new file mode 100644 index 0000000..9fc8cf3 --- /dev/null +++ "b/02.c++\347\254\224\350\256\260/08.C++\347\254\224\350\256\260.md" @@ -0,0 +1,10452 @@ + + +# 20210227 + +# 0.C++预习 + +## 1.helloworld + + + +![截屏2020-11-01 下午6.57.11](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-11-01%20%E4%B8%8B%E5%8D%887.01.38.png) + +![截屏2020-11-01 下午7.01.38](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-11-01%20%E4%B8%8B%E5%8D%887.01.38.png) + +![截屏2020-11-01 下午7.02.33](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-11-01%20%E4%B8%8B%E5%8D%887.01.38.png) + + + + + + + + + + + + + +![截屏2020-11-05 下午5.23.26](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-11-05%20%E4%B8%8B%E5%8D%885.23.26.png) + +![截屏2020-11-05 下午5.30.37](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-11-05%20%E4%B8%8B%E5%8D%885.30.37.png) + +![截屏2020-11-05 下午5.31.19](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-11-05%20%E4%B8%8B%E5%8D%885.31.19.png) + +![截屏2020-11-05 下午6.09.34](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-11-05%20%E4%B8%8B%E5%8D%886.09.34.png) + + + + + +## 2.if + + + + + + + + + + + + + + + + + + + +## 3.循环 + + + + + +## 4. 函数与递归 + + + + ![截屏2020-12-13 下午1.49.54](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-12-13%20%E4%B8%8B%E5%8D%881.49.54.png) + +![截屏2020-12-13 下午1.51.07](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-12-13%20%E4%B8%8B%E5%8D%881.51.07.png) + +![截屏2020-12-13 下午1.51.51](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-12-13%20%E4%B8%8B%E5%8D%881.51.51.png) + +![截屏2020-12-13 下午1.52.03](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-12-13%20%E4%B8%8B%E5%8D%881.52.03.png) + +![截屏2020-12-13 下午1.52.16](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-12-13%20%E4%B8%8B%E5%8D%881.52.16.png) + + + + + + + +## 5.类与对象 + + + +![截屏2020-12-10 上午11.32.30](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-12-10%20%E4%B8%8A%E5%8D%8811.32.30.png) + +![截屏2020-12-10 上午11.32.47](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-12-10%20%E4%B8%8A%E5%8D%8811.32.47.png) + +![截屏2020-12-10 上午11.33.00](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-12-10%20%E4%B8%8A%E5%8D%8811.33.00.png) + +![截屏2020-12-10 上午11.34.11](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-12-10%20%E4%B8%8A%E5%8D%8811.34.11.png) + +![截屏2020-12-10 上午11.34.31](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-12-10%20%E4%B8%8A%E5%8D%8811.34.31.png) + +![截屏2020-12-10 上午11.35.44](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-12-10%20%E4%B8%8A%E5%8D%8811.35.44.png) + +![截屏2020-12-10 上午11.36.17](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-12-10%20%E4%B8%8A%E5%8D%8811.36.17.png) + +![截屏2020-12-10 上午11.36.47](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-12-10%20%E4%B8%8A%E5%8D%8811.36.47.png) + +![截屏2020-12-10 下午1.51.06](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-12-10%20%E4%B8%8B%E5%8D%881.51.06.png) + +![截屏2020-12-10 下午2.11.51](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-12-10%20%E4%B8%8B%E5%8D%882.11.51.png) + +![截屏2020-12-10 下午2.19.53](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-12-10%20%E4%B8%8B%E5%8D%882.19.53.png) + +![截屏2020-12-10 下午2.21.48](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-12-10%20%E4%B8%8B%E5%8D%882.21.48.png) + +![截屏2020-12-10 下午2.22.03](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-12-10%20%E4%B8%8B%E5%8D%882.22.03.png) + +![截屏2020-12-10 下午2.23.35](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-12-10%20%E4%B8%8B%E5%8D%882.23.35.png) + +![截屏2020-12-10 下午4.27.37](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-12-10%20%E4%B8%8B%E5%8D%884.27.37.png) + +![截屏2020-12-10 下午4.28.44](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-12-10%20%E4%B8%8B%E5%8D%884.28.44.png) + +![截屏2020-12-10 下午4.30.31](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-12-10%20%E4%B8%8B%E5%8D%884.30.31.png) + +![截屏2020-12-10 下午4.32.13](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-12-10%20%E4%B8%8B%E5%8D%884.32.13.png) + +![截屏2020-12-10 下午4.36.29](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-12-10%20%E4%B8%8B%E5%8D%884.36.29.png) + +![截屏2020-12-10 下午4.40.48](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-12-10%20%E4%B8%8B%E5%8D%884.40.48.png) + +![截屏2020-12-10 下午4.47.07](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-12-10%20%E4%B8%8B%E5%8D%884.47.07.png) + +![截屏2020-12-10 下午4.48.16](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-12-10%20%E4%B8%8B%E5%8D%884.48.16.png) + +![截屏2020-12-10 下午4.48.42](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-12-10%20%E4%B8%8B%E5%8D%884.48.42.png) + +![截屏2020-12-10 下午4.49.19](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-12-10%20%E4%B8%8B%E5%8D%884.49.19.png) + +![截屏2020-12-10 下午4.49.53](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-12-10%20%E4%B8%8B%E5%8D%884.49.53.png) + +![截屏2020-12-10 下午5.05.14](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-12-10%20%E4%B8%8B%E5%8D%885.05.14.png) + + + + + + + +```cpp +#include +#include +using std::cin; +using std::cout; +using std::endl; +class Point{ +public: + Point(int newX = 0,int newY = 0){ + x = newX; + y = newY; + } + Point(Point &p){ + x = p.x; + y = p.y; + } + int getX() { + return x; + } + int getY() { + return y; + } +private: + int x,y; +}; +class Line{ +public: + Line(Point new_p1, Point new_p2):p1(new_p1),p2(new_p2) {//构造函数 + double x = static_cast(p1.getX() - p2.getX()); + double y = static_cast(p1.getY() - p2.getY()); + len = sqrt(x * x + y * y); +} + Line(Line &l):p1(l.p1),p2(l.p2){//复构函数 + len = l.len; + } + double getLen(){ + return len; + } +private: + Point p1, p2; + double len; +}; +int main(){ + Point pa(1, 2); + Point pb(3, 5); + Line L1(pa, pb); + Line L2(L1); + cout << "The length of L1 is " << L1.getLen() << endl; + cout << "The length of L2 is " << L2.getLen() << endl; + + + return 0; +} +``` + + + + + + + +![截屏2020-12-13 下午1.29.51](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-12-13%20%E4%B8%8B%E5%8D%881.29.51.png) + +![截屏2020-12-13 下午1.29.59](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-12-13%20%E4%B8%8B%E5%8D%881.29.59.png) + +![截屏2020-12-13 下午1.31.07](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-12-13%20%E4%B8%8B%E5%8D%881.31.07.png) + +![截屏2020-12-13 下午1.31.29](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-12-13%20%E4%B8%8B%E5%8D%881.31.29.png) + +![截屏2020-12-11 上午8.53.28](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-12-11%20%E4%B8%8A%E5%8D%888.53.28.png) + + + + + + + + + + + + + + + +## 6.数组、字符串、二维数组 + +![截屏2020-12-13 下午12.29.08](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-12-13%20%E4%B8%8B%E5%8D%8812.29.08.png) + + + + + +![截屏2020-12-13 下午1.01.34](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-12-13%20%E4%B8%8B%E5%8D%881.01.34.png) + +![截屏2020-12-13 下午1.13.12](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-12-13%20%E4%B8%8B%E5%8D%881.13.12.png) + +![截屏2020-12-13 下午1.14.15](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-12-13%20%E4%B8%8B%E5%8D%881.14.15.png) + +![截屏2020-12-13 下午1.14.54](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-12-13%20%E4%B8%8B%E5%8D%881.14.54.png) + +![截屏2020-12-13 下午1.18.27](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-12-13%20%E4%B8%8B%E5%8D%881.18.27.png) + + + + + + + +![截屏2020-12-13 下午1.01.34](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-12-13%20%E4%B8%8B%E5%8D%881.01.34.png) + +![截屏2020-12-13 下午1.13.12](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-12-13%20%E4%B8%8B%E5%8D%881.13.12.png) + +![截屏2020-12-13 下午1.14.15](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-12-13%20%E4%B8%8B%E5%8D%881.14.15.png) + +![截屏2020-12-13 下午1.14.54](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-12-13%20%E4%B8%8B%E5%8D%881.14.54.png) + +![截屏2020-12-13 下午1.18.27](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-12-13%20%E4%B8%8B%E5%8D%881.18.27.png) + +![截屏2020-12-13 下午1.19.59](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-12-13%20%E4%B8%8B%E5%8D%881.19.59.png) + + + + + + + + + + + + + +![截屏2020-12-13 下午12.32.14](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-12-13%20%E4%B8%8B%E5%8D%8812.55.34.png) + +![截屏2020-12-13 下午12.55.34](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-12-13%20%E4%B8%8B%E5%8D%8812.55.34.png) + + + +![截屏2020-12-13 下午1.35.09](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-12-13%20%E4%B8%8B%E5%8D%881.35.09.png) + +![截屏2020-12-13 下午1.36.06](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-12-13%20%E4%B8%8B%E5%8D%881.36.06.png) + + + + + + + +## 7.C语言风格的字符串与字符串操作 + + + + + +![截屏2020-12-13 下午1.46.16](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-12-13%20%E4%B8%8B%E5%8D%881.46.16.png) + +![截屏2020-12-13 下午2.08.01](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-12-13%20%E4%B8%8B%E5%8D%882.08.01.png) + +![截屏2020-12-13 下午2.09.32](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-12-13%20%E4%B8%8B%E5%8D%882.09.32.png) + +![截屏2020-12-13 下午4.07.53](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-12-13%20%E4%B8%8B%E5%8D%884.07.53.png) + + + + + + + + + +![截屏2020-12-13 下午11.33.02](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-12-13%20%E4%B8%8B%E5%8D%8811.33.02.png) + +![截屏2020-12-13 下午11.36.34](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-12-13%20%E4%B8%8B%E5%8D%8811.36.34.png) + +![截屏2020-12-13 下午11.37.03](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-12-13%20%E4%B8%8B%E5%8D%8811.37.03.png) + +![截屏2020-12-13 下午11.40.39](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-12-13%20%E4%B8%8B%E5%8D%8811.40.39.png) + +![截屏2020-12-13 下午11.42.24](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-12-13%20%E4%B8%8B%E5%8D%8811.42.24.png) + +![截屏2020-12-14 上午12.28.41](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-12-14%20%E4%B8%8A%E5%8D%8812.28.41.png) + +![截屏2020-12-14 上午12.28.49](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-12-14%20%E4%B8%8A%E5%8D%8812.28.49.png) + + + + + + + + + + + + + + + +## 8.Vector与STL简单入门 + + + + + + + +![截屏2020-12-13 下午6.25.09](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-12-13%20%E4%B8%8B%E5%8D%886.32.28.png) + +![截屏2020-12-13 下午6.28.29](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-12-13%20%E4%B8%8B%E5%8D%886.32.28.png) + +![截屏2020-12-13 下午6.32.28](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-12-13%20%E4%B8%8B%E5%8D%886.32.28.png) + + + + + +```cpp +#include +#include +using namespace std; + +int main() { + int m; + int n; + cin>>m>>n; + vector> matrix_a(m); + vector> matrix_b(n); + + int input; + for (int i = 0; i < m; ++i) { + for (int j = 0; j < n; ++j) { + cin >> input; + matrix_a[i].push_back(input); + } + } + for (int i = 0; i < n; ++i) { + for (int j = 0; j < m; ++j) { + cin >> input; + matrix_b[i].push_back(input); + } + } + for (int i = 0; i < m; ++i) { + for (int j = 0; j < m; ++j) { + int s = 0; + for (int k = 0; k < n; k++) { + s = s + matrix_a[i][k] * matrix_b[k][j]; + } + if(j != 0) { + cout << " "; + } + cout << s; + } + cout << endl; + } + + + + return 0; +} +``` + + + + + + + + + +![截屏2020-12-13 下午9.36.04](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-12-13%20%E4%B8%8B%E5%8D%889.36.04.png) + + + + + +```cpp +#include +#include +using namespace std; + +int main() { + string input; + cin >> input; + + cout << input << endl; + cout << input.length() << endl;//输出字符串涨肚 + cout << input[0] << endl;//重载的括号运算符 + + string str = "yangzhou301"; + auto result = input + str;//自动类型 推导 + cout << result << endl; + cout << (result < str) << endl; + + return 0; +} +``` + + + + + +![截屏2020-12-13 下午11.22.01](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-12-13%20%E4%B8%8B%E5%8D%8811.22.01.png) + + + + + +![截屏2020-12-13 下午11.24.40](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-12-13%20%E4%B8%8B%E5%8D%8811.24.40.png) + +![截屏2020-12-13 下午11.29.28](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-12-13%20%E4%B8%8B%E5%8D%8811.29.28.png) + +![截屏2020-12-13 下午11.30.08](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-12-13%20%E4%B8%8B%E5%8D%8811.30.08.png) + + + + + + + +## 9.指针与内存与对象的指针成员 + +![截屏2020-12-14 上午1.55.05](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-12-14%20%E4%B8%8A%E5%8D%881.55.05.png) + +![截屏2020-12-14 上午1.43.38](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-12-14%20%E4%B8%8A%E5%8D%881.43.38.png) + +![截屏2020-12-14 上午1.53.09](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-12-14%20%E4%B8%8A%E5%8D%881.53.09.png) + +![截屏2020-12-14 上午1.54.34](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-12-14%20%E4%B8%8A%E5%8D%881.54.34.png) + +![截屏2020-12-14 上午1.55.13](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-12-14%20%E4%B8%8A%E5%8D%881.55.13.png) + +![截屏2020-12-14 上午1.55.52](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-12-14%20%E4%B8%8A%E5%8D%881.55.52.png) + +![截屏2020-12-14 上午1.56.53](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-12-14%20%E4%B8%8A%E5%8D%881.56.53.png) + +![截屏2020-12-14 上午1.57.32](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-12-14%20%E4%B8%8A%E5%8D%881.57.32.png) + +![截屏2020-12-14 上午1.58.41](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-12-14%20%E4%B8%8A%E5%8D%881.58.41.png) + +![截屏2020-12-14 上午2.00.20](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-12-14%20%E4%B8%8A%E5%8D%882.00.20.png) + +![截屏2020-12-14 上午2.02.09](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-12-14%20%E4%B8%8A%E5%8D%882.02.09.png) + +![截屏2020-12-14 上午2.02.30](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-12-14%20%E4%B8%8A%E5%8D%882.02.30.png) + +![截屏2020-12-14 上午2.12.53](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-12-14%20%E4%B8%8A%E5%8D%882.12.53.png) + +![截屏2020-12-14 上午2.13.36](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-12-14%20%E4%B8%8A%E5%8D%882.13.36.png) + +![截屏2020-12-14 上午2.17.41](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-12-14%20%E4%B8%8A%E5%8D%882.17.41.png) + +![截屏2020-12-14 上午2.18.27](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-12-14%20%E4%B8%8A%E5%8D%882.18.27.png) + +![截屏2020-12-14 上午2.19.10](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-12-14%20%E4%B8%8A%E5%8D%882.19.10.png) + +![截屏2020-12-14 上午2.20.20](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-12-14%20%E4%B8%8A%E5%8D%882.20.20.png) + +![截屏2020-12-14 上午2.21.03](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-12-14%20%E4%B8%8A%E5%8D%882.21.03.png) + +![截屏2020-12-14 上午2.21.38](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-12-14%20%E4%B8%8A%E5%8D%882.21.38.png) + +![截屏2020-12-14 上午2.22.33](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-12-14%20%E4%B8%8A%E5%8D%882.22.33.png) + +![截屏2020-12-14 上午2.24.37](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-12-14%20%E4%B8%8A%E5%8D%882.24.37.png) + +![截屏2020-12-14 上午2.25.31](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-12-14%20%E4%B8%8A%E5%8D%882.25.31.png) + +![截屏2020-12-14 上午2.26.46](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-12-14%20%E4%B8%8A%E5%8D%882.26.46.png) + +![截屏2020-12-14 上午2.27.21](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-12-14%20%E4%B8%8A%E5%8D%882.27.21.png) + +![截屏2020-12-14 上午2.28.12](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-12-14%20%E4%B8%8A%E5%8D%882.28.12.png) + +![截屏2020-12-14 上午2.30.35](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-12-14%20%E4%B8%8A%E5%8D%882.30.35.png) + +![截屏2020-12-14 上午2.31.50](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-12-14%20%E4%B8%8A%E5%8D%882.31.50.png) + +![截屏2020-12-14 上午2.32.46](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-12-14%20%E4%B8%8A%E5%8D%882.32.46.png) + +![截屏2020-12-14 上午2.34.06](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-12-14%20%E4%B8%8A%E5%8D%882.34.06.png) + +![截屏2020-12-14 上午2.34.35](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-12-14%20%E4%B8%8A%E5%8D%882.34.35.png) + + + +![截屏2020-12-14 上午9.09.48](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-12-14%20%E4%B8%8A%E5%8D%889.09.48.png) + +![截屏2020-12-14 上午9.14.15](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-12-14%20%E4%B8%8A%E5%8D%889.14.15.png) + +![截屏2020-12-14 上午9.16.46](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-12-14%20%E4%B8%8A%E5%8D%889.16.46.png) + +![截屏2020-12-14 上午9.17.50](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-12-14%20%E4%B8%8A%E5%8D%889.17.50.png) + +![截屏2020-12-14 上午9.22.52](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-12-14%20%E4%B8%8A%E5%8D%889.22.52.png) + +![截屏2020-12-14 上午9.24.59](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-12-14%20%E4%B8%8A%E5%8D%889.24.59.png) + +![截屏2020-12-14 上午9.26.51](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-12-14%20%E4%B8%8A%E5%8D%889.26.51.png) + +![截屏2020-12-14 上午9.28.44](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-12-14%20%E4%B8%8A%E5%8D%889.28.44.png) + +![截屏2020-12-14 上午9.28.47](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-12-14%20%E4%B8%8A%E5%8D%889.28.47.png) + +![截屏2020-12-14 上午9.28.53](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-12-14%20%E4%B8%8A%E5%8D%889.28.53.png) + +![截屏2020-12-14 上午9.36.08](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-12-14%20%E4%B8%8A%E5%8D%889.36.08.png) + +![截屏2020-12-14 上午12.55.04](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-12-14%20%E4%B8%8A%E5%8D%8812.55.04.png) + +![截屏2020-12-14 上午12.58.52](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-12-14%20%E4%B8%8A%E5%8D%8812.58.52.png) + +![截屏2020-12-14 上午12.59.30](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-12-14%20%E4%B8%8A%E5%8D%8812.59.30.png) + + + + + + + +## 10、C/C++风格 + + + + + +![截屏2020-12-14 下午3.45.42](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-12-14%20%E4%B8%8B%E5%8D%883.45.42.png) + +![截屏2020-12-14 下午3.46.48](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-12-14%20%E4%B8%8B%E5%8D%883.46.48.png) + +![截屏2020-12-14 下午3.50.40](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-12-14%20%E4%B8%8B%E5%8D%883.50.40.png) + +![截屏2020-12-14 下午3.51.59](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-12-14%20%E4%B8%8B%E5%8D%883.51.59.png) + +![截屏2020-12-14 下午3.52.59](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-12-14%20%E4%B8%8B%E5%8D%883.52.59.png) + +![截屏2020-12-14 下午3.53.49](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-12-14%20%E4%B8%8B%E5%8D%883.53.49.png) + +![截屏2020-12-14 下午3.54.39](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-12-14%20%E4%B8%8B%E5%8D%883.54.39.png) + +![截屏2020-12-14 下午3.57.15](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-12-14%20%E4%B8%8B%E5%8D%883.57.15.png) + +![截屏2020-12-14 下午4.16.44](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-12-14%20%E4%B8%8B%E5%8D%884.16.44.png) + +![截屏2020-12-14 下午4.22.17](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-12-14%20%E4%B8%8B%E5%8D%884.22.17.png) + +![截屏2020-12-14 下午4.22.55](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-12-14%20%E4%B8%8B%E5%8D%884.22.55.png) + +![截屏2020-12-14 下午4.24.25](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-12-14%20%E4%B8%8B%E5%8D%884.24.25.png) + +![截屏2020-12-14 下午4.24.37](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-12-14%20%E4%B8%8B%E5%8D%884.24.37.png) + +![截屏2020-12-14 下午4.26.56](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-12-14%20%E4%B8%8B%E5%8D%884.26.56.png) + +![截屏2020-12-14 下午4.27.33](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-12-14%20%E4%B8%8B%E5%8D%884.27.33.png) + +![截屏2020-12-14 下午4.28.40](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-12-14%20%E4%B8%8B%E5%8D%884.28.40.png) + +![截屏2020-12-14 下午4.30.10](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-12-14%20%E4%B8%8B%E5%8D%884.30.10.png) + + + + + + + + + +## 11、 + + + + + + + + + + + + + + + + + + + + + + + +## C++标准模板库 + +1.迭代器 + + + +![截屏2020-12-14 下午4.55.45](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-12-14%20%E4%B8%8B%E5%8D%884.55.45.png) + + + + + +![截屏2020-12-14 下午4.58.44](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2020-12-14%20%E4%B8%8B%E5%8D%884.58.44.png) + + + + + + + + + + + + + + + + + + + + + + + +# 1. 从C到C++ + + + +> **注:PPT来至于开课吧胡船长PPT,请注意版权** + + + +## 1.1 C++ + +C++03—>C++11(差距大)—>c++14—>c++17(差距小)—>c++20(最新版) + + + + + +C++继承了C语言的一部分(标准头文件) + +标准头文件 + +![截屏2021-02-28 上午10.06.28](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-28%20%E4%B8%8A%E5%8D%8810.06.28.png) + +类和对象:为了支持类和对象编程范式 + +模板:支持泛型编程 + +Lamba:支持函数式编程 + +STL:惠普工程师开发的c++,后来纳入C++标准 + + + +> C++支持面向过程、面向对象编程、泛型编程、函数式编程,四种编程范式 + + + +编程范式:提高开发效率 + +异常处理机制:解决奔溃退出的场景 + + + +面向对象比面向过程更好维护 + + + + + + + +## 1.2 C++学习重点 + + + +| | C语言 | | C++语言 | +| ------------ | ----- | ---- | ------- | +| 面向过程编程 | **√** | | **√** | +| 面向对象编程 | **×** | | **√** | +| 泛型编程 | **×** | | **√** | +| 函数式编程 | × | | **√** | + + + +> 如何学习? +> +> 按照编程范式分类学习 + + + +书籍:C++Primer、C++Primer Plus、Effective C++(改善程序设计的55个具体做法) + + + + + +## 1.3 程序对比 + + + +![截屏2021-02-28 上午10.51.55](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-28%20%E4%B8%8A%E5%8D%8810.51.55.png) + + + +## 1.4 queue 类说明 + +![截屏2021-02-28 上午10.56.37](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-28%20%E4%B8%8A%E5%8D%8810.56.37.png) + + + +## 1.5 deque + +![截屏2021-02-28 下午1.26.30](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-28%20%E4%B8%8B%E5%8D%881.26.30.png) + + + + + +## 1.6 string + +![截屏2021-02-28 下午3.33.24](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-28%20%E4%B8%8B%E5%8D%883.33.24.png) + + + +## 1.7 undered_map + +![截屏2021-02-28 下午3.35.10](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-28%20%E4%B8%8B%E5%8D%883.35.10.png) + +unordered_map:无序,底层用哈希表实现 + +map:底层用红黑树实现 + +## 1.8 编码规范 + +![截屏2021-02-28 下午3.35.32](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-28%20%E4%B8%8B%E5%8D%883.35.32.png) + + + + + +## 1.9 Effective C++ + +![截屏2021-02-28 下午3.53.19](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-28%20%E4%B8%8B%E5%8D%883.53.19.png) + +![截屏2021-02-28 下午3.53.28](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-28%20%E4%B8%8B%E5%8D%883.53.28.png) + +![截屏2021-02-28 下午3.58.29](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-28%20%E4%B8%8B%E5%8D%883.58.29.png) + + + +```cpp +#include +using namespace std; + +class A{ +public: + A() { + x = 123; + y = 0; + z = 0; + } + void say1() const { + cout << x << endl; + //x = 2344;//不能修改 + //assignment of member 'A::x' in read-only object + //y += 1;//不能修改 + //assignment of member 'A::y' in read-only object + z += 1;//逻辑意义上的const + } + void say2() { + x = 456;//非const方法可以修改数据 + } + int x; + //int y;//say1()方法被调用了多少次 + mutable int z;//say1()方法被调用了多少次 +}; + + +int main() { + const int a = 1;//数据上的const + //a = 3;//assignment of read-only variable 'a' + + const A b; + b.say1(); + //b.say2(); + //const类型变量只能调用const方法,其他方法可能会修改数据,所以不能被调用 + //passing 'const A' as 'this' argument discards qualifiers [-fpermissive] + b.say1(); + b.say1(); + b.say1(); + b.say1(); + b.say1(); + + return 0; +} +``` + + + +```cpp +0 +1 +2 +3 +4 +``` + + + +核心的数据没有变就是逻辑上的常量(const) + + + +![截屏2021-02-28 下午4.50.00](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-28%20%E4%B8%8B%E5%8D%884.50.00.png) + + + +```cpp +#include +using namespace std; + +class A{ +public: + A() : x(123), y(x - 1) {}//实际顺序=代码书写顺序 + #if 0 + A() : y(x - 1), x(123) {//构造, 按顺序赋值,避免出现代码逻辑上的bug, + //x = 123;//赋值 + //y = 0; + }//可能会出错 + #endif + void say1() const { + cout << "x = " << x << ", y = " << y << endl; + } + int x; + int y; +}; + + +int main() { + A a; + a.say1(); + + + return 0; +} +``` + + + + + +## 练习1:haizeioj166 string类的使用 + +![截屏2021-03-01 下午1.29.07](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-03-01%20%E4%B8%8B%E5%8D%881.29.07.png) + +![截屏2021-03-01 下午1.29.15](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-03-01%20%E4%B8%8B%E5%8D%881.29.15.png) + + + +```cpp +#include +#include + + +using namespace std; + +int main() { + string s1, s2; + int n; + cin >> s1 >> n >> s2; + if (s1.size() < 100) cout << s1.size() << endl; + else cout << 100 << endl; + s1.insert(n - 1, s2); + cout << s1 << endl; + cout << s1.size() - s1.rfind("x") << endl; + return 0; +} + +``` + + + +## 练习2:haizeioj245 c++排序功能 + +![截屏2021-03-01 下午1.48.09](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-03-01%20%E4%B8%8B%E5%8D%881.48.09.png) + +```cpp +#include +#include +using namespace std; + +int main() { + vector arr; + int n; + cin >> n; + for (int i = 0, a; i < n; i++) { + cin >> a; + arr.push_back(a); + } + sort(arr.begin(), arr.end()); + //nth_element(); + int pos = arr[n / 2]; + int sum = 0; + for (int i = 0; i < arr.size(); i++) { + sum += abs(arr[i] - pos); + } + cout << sum << endl; + + + return 0; +} +``` + + + +## 练习3:haizeioj216 c++排序 map + +![截屏2021-03-01 下午2.13.37](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-03-01%20%E4%B8%8B%E5%8D%882.13.37.png) + +![截屏2021-03-01 下午2.13.46](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-03-01%20%E4%B8%8B%E5%8D%882.13.46.png) + +```cpp +#include +#include +using namespace std; + +int main() { + string s; + map h; + int n; + cin >> n; + for (int i = 0; i < n; i++) { + cin >> s; + h[s.substr(3, s.size())] += 1; + } + + for (auto iter = h.begin(); iter != h.end(); iter++) { + for (int i = 0; i < iter->second; i++) cout << iter->first << endl; + } + return 0; +} +``` + + + + + +## 作业3:haizeioj287 合并果子 + +![截屏2021-03-01 下午2.38.08](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-03-01%20%E4%B8%8B%E5%8D%882.38.08.png) + +![截屏2021-03-01 下午2.38.20](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-03-01%20%E4%B8%8B%E5%8D%882.38.20.png) + + + +```cpp +#include +#include +using namespace std; + +struct CMP { +public: + bool operator()(int a, int b) { + return a > b; + }//重载优先队列排序规则 +}; + +int main() { + priority_queue, CMP> q;//优先队列 + int n; + cin >> n; + for (int i = 0, a; i < n; i++) { + cin >> a; + q.push(a); + } +#if 0 + while (!q.empty()) { + cout << q.top() << endl; + q.pop(); + } +#endif + int sum = 0; + for (int i = 1; i < n; i++) { + int a = q.top(); q.pop(); + int b = q.top(); q.pop(); + sum += a + b; + q.push(a + b); + } + cout << sum << endl; + + return 0; +} +``` + + + +## 练习4:haizei256. 国王游戏 + +![截屏2021-03-02 下午6.05.20](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-03-02%20%E4%B8%8B%E5%8D%886.05.20.png) + +![截屏2021-03-02 下午6.05.37](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-03-02%20%E4%B8%8B%E5%8D%886.05.37.png) + + + +![截屏2021-03-02 下午5.45.46](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-03-02%20%E4%B8%8B%E5%8D%885.45.46.png) + + + +```cpp +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +using namespace std; + + + +typedef pair PII; + +class BigInt : public vector { +public: + BigInt(int x) { + push_back(x); + proccess_digit(); + } + void operator*=(int x); + void operator/=(int x); + BigInt operator/(int x); + bool operator<(const BigInt &) const; + bool operator>(const BigInt &) const; +private: + void proccess_digit(); +}; +#if 0 +BigInt::BigInt(x) { + push_back(x); + proccess_digit(); +} +#endif + +ostream &operator<<(ostream &out, const BigInt &a) {//重载左移运算符 + for (int i = a.size() - 1; i >= 0; i--) { + out << a[i]; + } + return out; +} + +void BigInt::proccess_digit() {//处理进位 + for (int i = 0; i < size(); i++) { + if (at(i) < 10) continue; + if (i + 1 == size()) push_back(0); + at(i + 1) += at(i) / 10; + at(i) %= 10; + } + while (size() > 1 && at(size() - 1) == 0) pop_back(); + return ; +} + +void BigInt::operator*=(int x) {// *= + for (int i = 0; i < size(); i++) at(i) *= x; + proccess_digit(); + return ; +} + +void BigInt::operator/=(int x) { + int y = 0; + for (int i = size() - 1; i >= 0; i--) {// /= + y = y * 10 + at(i); + at(i) = y / x; + y %= x; + } + proccess_digit(); + return ; +} + +BigInt BigInt::operator/(int x) {//除法 + BigInt ret(*this); + ret /= x; + return ret; +} + +bool BigInt::operator<(const BigInt &a) const{ + if (size() - a.size()) return size() < a.size();//位数不相同,直接返回 + for (int i = size() - 1; i >= 0; i--) { + if (at(i) - a[i]) return at(i) < a[i]; + } + return false; +} + +bool BigInt::operator>(const BigInt &a) const { + return a < (*this); +} + +int main() { + vector arr; + int n; + cin >> n; + + for (int i = 0, a, b; i <= n; i++) { + cin >> a >> b; + arr.push_back(PII(a, b)); + } + sort(arr.begin() + 1, arr.end(), + [](const PII &x, const PII &y){ + return x.first * x.second < y.first * y.second; + } + ); + + BigInt p = arr[0].first;//当前累乘结果 + BigInt ans = 0;//获得最多金币者获得多少金币 + + for (int i = 1; i <= n; i++) { + BigInt temp = p / arr[i].second; + ans = max(ans, temp); + p *= arr[i].first; + } + + cout << ans << endl; + + + return 0; +} + + +``` + + + + + +## 作业1:nth_element() + +```cpp +nth_element() +``` + + + +> 通过调用nth_element(start, start+n, end) +> 方法可以使第n大元素处于第n位置(从0开始,其位置是下标为n的元素),并且比这个元素小的元素都排在这个元素之前,比这个元素大的元素都排在这个元素之后,但不能保证他们是有序的,默认用 `<` 运算符来生成这个结果 + +eg: + +```cpp +#include +#include +#include +#include + +int main() +{ + std::vector v{5, 6, 4, 3, 2, 6, 7, 9, 3}; + + std::nth_element(v.begin(), v.begin() + v.size()/2, v.end()); + std::cout << "The median is " << v[v.size()/2] << '\n'; + for (int i = 0; i < v.size(); i++) std::cout << v[i] << " "; + std::cout << std::endl; + + std::nth_element(v.begin(), v.begin()+1, v.end(), std::greater()); + std::cout << "The second largest element is " << v[1] << '\n'; + for (int i = 0; i < v.size(); i++) std::cout << v[i] << " "; + std::cout << std::endl; +} + +``` + +结果分析 + +``` +The median is 5 +3 2 3 4 5 6 7 9 6 +The second largest element is 7 +9 7 6 6 5 3 4 3 2 +``` + +第一中用法处于第`v.begin()+v,size()/2=5`位置的元素是5,输出排序结果,前面的都比5小,后面的都比5大,但是我们没有顺序 + +第二种用法修改了排序方法,默认的从小到大, + +nth_element(begin, begin+n,end,std::greater());修改成从大到小的规律 + + + + + + + + + + + + + +## 作业2:string三种方法相关解释和代码演示 + + + +string::find_first_of函数搜寻参数字符串中 的出现。 + + + + + +```cpp +#include +#include +using namespace std; + +int main() { + string str = "I am guziqiu. I don't like c++."; + string name = "我是古子秋!"; + + + cout << "1.find(am):" << str.find("am") << endl; + str.insert(0,name); + cout << "2.insert(name):" << str << endl; + cout << "3.substr(9, 10):" << str.substr(9, 10) << endl; + cout << endl; + + str.append(name); + cout << str << endl; + + int i = 7; + + str.replace(str.find(" "), 1, "%20"); + cout << "4.删除从str.find()开始的1个字符,然后在str.find()处插入串%20" << endl; + cout << str << endl; + cout << "5.swap(name),互换name和str的内容:"; + str.swap(name); + cout << "str = " << str << "name = " << name << endl; + cout << "6.assign(asd)修改str内容为asd:"; + str.assign("asd"); + cout << str << endl; + return 0; +} +``` + + + +```cpp +1.find(am):2 +2.insert(name):我是古子秋!I am guziqiu. I don't like c++. +3.substr(9, 10):子秋!I a + +我是古子秋!I am guziqiu. I don't like c++.我是古子秋! +4.删除从str.find()开始的1个字符,然后在str.find()处插入串%20 +我是古子秋!I%20am guziqiu. I don't like c++.我是古子秋! +5.swap(name),互换name和str的内容:str = 我是古子秋!name = 我是古子秋!I%20am guziqiu. I don't like c++.我是古子秋! +6.assign(asd)修改str内容为asd:asd +``` + + + + + + + +## 作业3:讨论合并果子和哈夫曼编码的关系 + + + +哈弗曼编码是选取两棵根结点权值最小的树作为左右子树构造一棵新的二叉树, 且置新的二叉树的根结点的权值为左右子树根结点的权值之和。而合并果子每次选择两个水果堆数量最少的堆进行合并,可以看出哈夫曼编码和合并水果的方法是完全相同的,都是从一堆数字中选择两个最小的数字相加,然后将它们放回堆中。 + +![截屏2021-03-11 下午12.03.16](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-03-11%20%E4%B8%8B%E5%8D%8812.03.16.png) + + + + + +# 2.封装 + + + +## 2.1 类和对象 + +![截屏2021-03-03 上午10.05.16](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-03-03%20%E4%B8%8A%E5%8D%8810.05.16.png) + + + +> 类型 = 数据==类型== + 数据==操作== +> +> 数据结构 = 数据定义 + 结构操作 + + + +![截屏2021-03-03 上午10.30.56](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-03-03%20%E4%B8%8A%E5%8D%8810.30.56.png) + +![截屏2021-03-03 上午10.31.39](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-03-03%20%E4%B8%8A%E5%8D%8810.31.39.png) + +![截屏2021-03-03 上午10.33.55](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-03-03%20%E4%B8%8A%E5%8D%8810.33.55.png) + + + + + +使用using namespace std + + + +## 2.2 代码演示 + +```cpp +#include +#include +using std::cout; +using std::cin; +using std::endl; +using std::string; + +class Cat {}; + +class Dog {}; + +class People {//默认访问权限是private,类内部 +public: + string name; + int age; + double height; + double weight; + + void say(string name);//声明 +#if 0 + void say() {//实现在内部 + cout << "my name is " << name << endl; + } +#endif + void run(); +}; + +void People::say(string name) {//实现在外部 + cout << " this = " << this << endl;//this指针,只能在成员方法内部访问,发现this指针指向对象的地址 + cout << "my name is " << this->name << " " << name << endl; + //this指针强调使用的成员属性,this->name成员属性,name指参数 +} + +int main() { + People guziqiu; + People apricity; + + guziqiu.name = "古子秋";//类外部,默认访问权限是private, 类外部不会访问,需要加上public + apricity.name = "Apricity"; + + cout << " &guziqiu = " << &guziqiu << endl; + guziqiu.say("aha"); + cout << "&apricity = " << &apricity << endl; + apricity.say("aha"); + + Cat cat; + + return 0; +} +``` + + + +```cpp +//运行结果 + &guziqiu = 0x7ffee72c47c0 + this = 0x7ffee72c47c0 +my name is 古子秋 aha +&apricity = 0x7ffee72c4780 + this = 0x7ffee72c4780 +my name is Apricity aha +``` + + + + + +## 命名空间 + +> ### 什么是命名空间? + +命名空间就像是很多不同的家庭,不同的人放在不同的家庭,命名空间区分不同家庭的张三。可以认为class是一个特殊的命名空间,eg: + +```cpp +#include +#include +using std::cout; +using std::cin; +using std::endl; +using std::string; + +namespace haizei{ + class Cat {}; + + class Dog {}; + + class People {//默认访问权限是private,类内部 + public: + string name; + int age; + double height; + double weight; + + void say(string name);//声明 + #if 0 + void say() {//实现在内部 + cout << "my name is " << name << endl; + } + #endif + void run(); + }; + + void People::say(string name) {//实现在外部 + cout << " this = " << this << endl;//this指针,只能在成员方法内部访问,发现this指针指向对象的地址 + cout << "my name is " << this->name << " " << name << endl; + //this指针强调使用的成员属性,this->name成员属性,name指参数 + } +}// end of haizei + +using namespace haizei; +int main() { + haizei::People guziqiu;//写法1 ==> People::say People 是特殊的命名空间 + People apricity;//写法2,使用using namespace haizei; 不提倡 + + guziqiu.name = "古子秋";//类外部,默认访问权限是private, 类外部不会访问,需要加上public + apricity.name = "Apricity"; + + cout << " &guziqiu = " << &guziqiu << endl; + guziqiu.say("aha"); + cout << "&apricity = " << &apricity << endl; + apricity.say("aha"); + + haizei::Cat cat; + + return 0; +} + + +``` + + + +## 2.3 实现简单的cout + + + +```cpp +#include +#include + +#define BEGINS(x) namespace x { +#define ENDS(x) } // namespace x +BEGINS(haizei) + +class ostream { +public : + ostream &operator<<(int x); + ostream &operator<<(const char *x); +}; + +ostream &ostream::operator<<(int x) { + printf("%d", x); + return *this; +} +ostream &ostream::operator<<(const char *x) { + printf("%s", x); + return *this; +} + +ostream cout; + +ENDS(haizei) + +int main() { + int n = 123, m = 456; + std::cout << n << " " << m; std::cout << std::endl; + haizei::cout << n << " " << m; std::cout << std::endl; + + return 0; +} + + +``` + + + + + +> cout本质是什么? +> +> 接在左移运算符左侧,是一个对象 +> +> 有返回值 +> +> + + + +## 2.4 构造函数与析构函数 + + + +![截屏2021-03-06 上午8.41.55](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-03-06%20%E4%B8%8A%E5%8D%888.41.55.png) + +构造函数:负责函数初始化 + +析构函数:负责函数销毁 + + + +任何对象的生命周期一个会调用构造函数,最后一定调用析构函数。 + + + +## 2.5 代码演示 + + + +### C++学习重点:程序的处理流程 + + + +### 1.析构和构造 + +```cpp +#include +using namespace std; + +class A { +public: + A() { + cout << this << " constructor()" << endl; + } + ~A() {//析构函数在main函数执行完后调用 + cout << this <<" ~destructor()" << endl; + } + +private: + +}; + +int main() { + A a; + A b; + + cout << "a : " << &a << endl; + cout << "b : " << &b << endl; + cout << "end of main" << endl; + return 0; +} + + +``` + + + +```cpp +0x7ffeedbc85df constructor() +0x7ffeedbc85de constructor() +a : 0x7ffeedbc85df +b : 0x7ffeedbc85de +end of main +0x7ffeedbc85de ~destructor() +0x7ffeedbc85df ~destructor() +``` + + + +> 先定义先构造,构造的时候B对象可能依赖A对象的信息, +> +> 先定义后析构,B析构的时候可能依赖于A对象的信息 + + + +### 2.转换构造:在初始化时 + +```cpp +#include +using namespace std; + +class A { +public: + A() { + cout << this << " constructor()" << endl; + } + A(int x) {// 转换构造 + cout << this << " : transform constructor" << endl; + } + A &operator=(const A &a) { + cout << this << " : operator=" << endl; + return *this; + } + + ~A() {//析构函数在main函数执行完后调用 + cout << this <<" ~destructor()" << endl; + } + +private: + +}; + +int main() { + A c(3); + A d = 4;// 转换构造, 一般相同类型的才能赋值,赋值给一个对象,说明把这个值转换成了一个A类型,也就是转换构造 + cout << "c : " << &c << endl; + cout << "d : " << &d << endl; + cout << "end of main" << endl; + return 0; +} + + +``` + +```cpp +0x7ffeea7625df : transform constructor +0x7ffeea7625de : transform constructor +c : 0x7ffeea7625df +d : 0x7ffeea7625de +end of main +0x7ffeea7625de ~destructor() +0x7ffeea7625df ~destructor() +``` + + + +> 转换构造, 一般相同类型的才能赋值,赋值给一个对象,说明把这个值转换成了一个A类型,也就是转换构造 + + + +### 3.对象赋值:初始化后的赋值操作 + +```cpp +#include +using namespace std; + +class A { +public: + A() { + cout << this << " constructor()" << endl; + } + A(int x) {// 转换构造 + cout << this << " : transform constructor" << endl; + } + A &operator=(const A &a) { + cout << this << " : operator=" << endl; + return *this; + } + ~A() {//析构函数在main函数执行完后调用 + cout << this <<" ~destructor()" << endl; + } +private: + +}; + +int main() { + A a; + a = 3;// 转换成临时匿名对象 x(调用转换构造),然后绑定到a,最后this指向main函数中的a + cout << "a : " << &a << endl; + cout << "end of main" << endl; + return 0; +} + + +``` + + + +```cpp +0x7ffee5a575de constructor() +0x7ffee5a575df : transform constructor//临时匿名对象 +0x7ffee5a575de : operator= +0x7ffee5a575df ~destructor()//临时匿名对象 +a : 0x7ffee5a575de +end of main +0x7ffee5a575de ~destructor() +``` + + + +> 转换成零时匿名对象 x(调用转换构造),然后绑定到a,最后this指向main函数中的a + + + + + +### 4.拷贝构造 + +```cpp +#include +using namespace std; +void add_one(int x) { + x += 1; + return ; +} +void add_one1(int &x) {// C++C++&:相当于给变量起别名 ,传引用不产生拷贝行为 + x += 1; + return ; +} + +int main() { + + int n = 3; + add_one(n); + cout << n << endl; + add_one1(n); + cout << n << endl; + return 0; +} +``` + +``` +3 +4 +``` + + + + + +```cpp +#include +using namespace std; + +class A { +public: + A() { + cout << this << " constructor()" << endl; + } + A(int x) {// 转换构造 + cout << this << " : transform constructor" << endl; + } + A &operator=(const A &a) { + cout << this << " : operator=" << endl; + return *this; + } + + A(A a1) {}// 此处会报错 + + ~A() {//析构函数在main函数执行完后调用 + cout << this <<" ~destructor()" << endl; + } + +private: + +}; + +int main() { + A a; + A b = a;// 定义行:此处不论是写A b = a;或者A b(a); 都会调用拷贝构造 + return 0; +} + + +``` + + + +> 为什么会报错? +> +> 调用b对象的拷贝构造, b对象的拷贝构造需要传一个参数a1, 也就是 a1 = a,这个过程会调用a1的拷贝构造A(A a1) {} +> +> a1的拷贝构造调用a1的拷贝构造,造成死循环, + + + +正确写法 + +```cpp +#include +using namespace std; + +class A { +public: + A() { + cout << this << " constructor()" << endl; + } + A(int x) {// 转换构造 + cout << this << " : transform constructor" << endl; + } + A &operator=(const A &a) { // 对象赋值 + cout << this << " : operator=" << endl; + return *this; + } + + // A(A a1) {} + A(const A &a) { // 避免传过来的变量是一个临时变量 + cout << this << " : copy constructor" << endl;; + } + + + + ~A() {//析构函数在main函数执行完后调用 + cout << this <<" ~destructor()" << endl; + } + +private: + +}; + + +int main() { + + + A a; + A b = a;// 定义行:此处不论是写A b = a;或者A b(a); 都会调用拷贝构造 + b = a;// 调用赋值运算符 + // 调用b对象的拷贝构造, b对象的拷贝构造需要传一个参数a1, 也就是 a1 = a,这个过程会调用a1的拷贝构造A(A a1) {} + // a1的拷贝构造调用a1的拷贝构造,造成死循环, + // 拷贝构造不能用值传递 + // 传引用不产生拷贝行为 + + cout << "a : " << &a << endl; + cout << "b : " << &b << endl; + cout << "end of main" << endl; + return 0; +} + + +``` + + + +```cpp +0x7ffee903b5df constructor() +0x7ffee903b5de : copy constructor +0x7ffee903b5de : operator= +a : 0x7ffee903b5df +b : 0x7ffee903b5de +end of main +0x7ffee903b5de ~destructor() +0x7ffee903b5df ~destructor() +``` + + + + + +> `A b = a;` 定义行:此处不论是写A b = a;或者A b(a); 都会调用拷贝构造 +> +> `b = a;` 调用赋值运算符 +> +> 调用b对象的拷贝构造, b对象的拷贝构造需要传一个参数a1, 也就是 a1 = a,这个过程会调用a1的拷贝构造A(A a1) {} +> +> ​ a1的拷贝构造调用a1的拷贝构造,造成死循环, +> +> ​ 拷贝构造不能用值传递 +> +> ​ 传引用不产生拷贝行为 + + + +> 为什么是拷贝构造的参数要加const? +> +> 处理传过来的参数是const类型 + +```cpp +#include +using namespace std; + +class A { +public: + A() { + cout << this << " constructor()" << endl; + } + A(int x) {// 转换构造 + cout << this << " : transform constructor" << endl; + } + const A &operator=(const A &a) const { + cout << this << " : operator=" << endl; + return *this;// 返回const类型指针 + } + + // A(A a1) {} + A(const A &a) { // 避免传过来的变量是一个临时变量,同时也可以处理传过来的参数是const类型 + cout << this << " : copy constructor" << endl;; + } + + + + ~A() {//析构函数在main函数执行完后调用 + cout << this <<" ~destructor()" << endl; + } + +private: + +}; + +void add_one(int x) { + x += 1; + return ; +} +void add_one1(int &x) {// 传引用不产生拷贝行为 + x += 1; + return ; +} + +int main() { + + const A a; + A b = a; + A c = 3; + return 0; +} + + +``` + + + + + +### 5.深拷贝和浅拷贝 + + + +#### 浅拷贝 + +```cpp +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +using namespace std; + +class Array { +public: + Array(int n = 10) { + this->n = n; + data = new int[n]; + // new会调用构造函数,new是运算符,可以重载(malloc不会调用构造函数,malloc是函数) + } + size_t size() const { + return this->n; + } + + int &operator[](int ind) { + if (ind < 0 || ind >= n) return end; + return data[ind]; + } + + const int &operator[](int ind) const { + if (ind < 0 || ind >= n) return this->end; + return this->data[ind]; + } + +private: + int *data; + size_t n; + int end; +}; + +ostream &operator<<(ostream &out, const Array &a) {// const 类型的引用只能调用const类型的方法 + out << "Array(" << &a << ") : "; + for (int i = 0; i < a.size(); i ++) { + i && out << " "; + cout << a[i]; + } + return out; +} + + +int main() { + + Array a; + for (int i = 0; i < a.size(); i++) { + a[i] = rand() % 100; + } + + cout << a << endl; + + Array b = a; + cout << b << endl; + b[1] = 16384; + cout << a << endl; + cout << b << endl; + + + return 0; +} + +``` + + + +```cpp +Array(0x7ffee51d65c0) : 7 49 73 58 30 72 44 78 23 9 +Array(0x7ffee51d65a0) : 7 49 73 58 30 72 44 78 23 9 +Array(0x7ffee51d65c0) : 7 16384 73 58 30 72 44 78 23 9 +Array(0x7ffee51d65a0) : 7 16384 73 58 30 72 44 78 23 9 +``` + + + +> 现象:修改b对象的值后a对象的值也改变了 +> +> 为什么? +> +> 经行了简单的赋值(默认的拷贝构造,实现的是浅拷贝),data类型是指针,他们指向同一个地址,所以他们的值同时改变了 + + + +#### 深拷贝 + +```cpp +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +using namespace std; + +class Array { +public: + Array(int n = 10) { + this->n = n; + data = new int[n]; // new会调用构造函数,new是运算符,可以重载(malloc不会调用构造函数,malloc是函数) + } + Array(const Array &a) { // 深拷贝 + this->n = a.n; + this->data = new int[this->n]; + for (int i = 0; i < this->n; i++) { + this->data[i] = a.data[i]; + } + } + size_t size() const { + return this->n; + } + + int &operator[](int ind) { + if (ind < 0 || ind >= n) return end;// 访问数组越界 + return data[ind]; + } + + const int &operator[](int ind) const { + if (ind < 0 || ind >= n) return this->end;// 访问数组越界 + return this->data[ind]; + } + +private: + int *data; // 存储数据 + size_t n; // 数组大小 + int end; // 数组访问越界返回end +}; + +ostream &operator<<(ostream &out, const Array &a) {// const 类型的引用只能调用const类型的方法 + out << "Array(" << &a << ") : "; + for (int i = 0; i < a.size(); i ++) { + i && out << " "; + cout << a[i]; + } + return out; +} + + +int main() { + + Array a; + for (int i = 0; i < a.size(); i++) { + a[i] = rand() % 100; + } + + cout << a << endl; + + Array b = a; + cout << b << endl; + b[1] = 16384; + cout << a << endl; + cout << b << endl; + + + return 0; +} + +``` + +```cpp +Array(0x7ffee881e5c0) : 7 49 73 58 30 72 44 78 23 9 +Array(0x7ffee881e5a0) : 7 49 73 58 30 72 44 78 23 9 +Array(0x7ffee881e5c0) : 7 49 73 58 30 72 44 78 23 9 +Array(0x7ffee881e5a0) : 7 16384 73 58 30 72 44 78 23 9 +``` + + + +> 拷贝的时候为新的对象申请了新的空间 + + + +### 6. 功能上的构造和编译器所谓的构造 + + + +```cpp +Array(int n = 10) { + this->n = n; + data = new int[n]; +} +``` + + + +> 功能上的构造:需要执行完第4行 +> +> 编译器所谓的构造: 需要执行完第一行代码,执行完第一行后变量就可以使用了 + + + +```cpp +#include +using namespace std; + +class Data { // 数据类 +public: + + // Data(int x, int y) {// 没有默认构造,当添加了有参构造的时候,编译器默认添加的无参构造就会被删除, + // this->x = x; + // this->y = y; + // } + Data(int x, int y) : x(x), y(y) {}// 在构造列表初始化,保证在大括号之内已经构造完对象 + + +private: + int x, y; +}; + + + +class A { +public: + Data d; + A() : d(3, 4){// :d(3, 4) 初始化列表,初始化对象的每一个属性,初始化列表执行完后,对象已经构造完了 + this->d; + cout << this << " constructor()" << endl; + } + A(int x) : d(x, x) {// 转换构造 + cout << this << " : transform constructor" << endl; + } + A(const A &a) : d(a.d) { + cout << this << " : copy constructor" << endl;; + } + + const A &operator=(const A &a) const { + cout << this << " : operator=" << endl; + return *this; + } + + ~A() { + cout << this <<" ~destructor()" << endl; + } + +private: + +}; + + +int main() { + return 0; +} + + +``` + + + + + +> 没有默认构造,当添加了有参构造的时候,编译器默认添加的无参构造就会被删除 +> +> 在构造列表初始化,保证在大括号之内已经构造完对象 +> +> `A() : d(3, 4){ // :d(3, 4)` 初始化列表,初始化对象的每一个属性,初始化列表执行完后,对象已经构造完了,确保可以在大括号内调用 + + + +### 7.初始化列表 + + + +```cpp +#include +using namespace std; + +class Data { // 数据类 +public: + + // Data(int x, int y) {// 没有默认构造,当添加了有参构造的时候,编译器默认添加的无参构造就会被删除, + // this->x = x; + // this->y = y; + // } + Data(int x, int y) : x(x), y(y) { + cout << this << endl; + }// 在构造列表初始化,保证在大括号之内已经构造完对象 +private: + int x, y; +}; + + + +class A { +public: + Data c, d; + A() : d(3, 4), c(3, 4) { + // :d(3, 4) 初始化列表,初始化对象的每一个属性,初始化列表执行完后,对象已经构造完了 + cout << this << " constructor()" << endl; + cout << " c : " << &c << endl; + cout << " d : " << &d << endl; + } + A(int x) : d(x, x), c(3, 4) {// 转换构造 + cout << this << " : transform constructor" << endl; + } + // A(A a1) {} + A(const A &a) : d(a.d), c(3, 4) { // 避免传过来的变量是一个临时变量,同时也可以处理传过来的参数是const类型 + cout << this << " : copy constructor" << endl;; + } + + const A &operator=(const A &a) const { + cout << this << " : operator=" << endl; + return *this; + } + + ~A() {//析构函数在main函数执行完后调用 + cout << this <<" ~destructor()" << endl; + } + +private: + +}; + +int main() { + const A a; + A b = a; + A c = 3; + return 0; +} + + +``` + + + +```cpp +0x7ffee061b5d0 +0x7ffee061b5d8 +0x7ffee061b5d0 constructor() + c : 0x7ffee061b5d0 + d : 0x7ffee061b5d8 +0x7ffee061b5c0 +0x7ffee061b5c0 : copy constructor +0x7ffee061b5b0 +0x7ffee061b5b8 +0x7ffee061b5b0 : transform constructor +0x7ffee061b5b0 ~destructor() +0x7ffee061b5c0 ~destructor() +0x7ffee061b5d0 ~destructor() +``` + + + +> 结论: +> +> cd对象构造的顺序是由申明的顺序决定的与初始化列表顺序无关 +> +> 所以说`A() : d(3, 4), c(3, 4)`和`A() : c(3, 4), d(3, 4)`是没有区别的, +> +> `Data c, d;`一定会先构造对象c, +> +> `Data d(c), c(3, 4);` 也就说可以把c拷贝给d +> +> `Data c(d), d(3, 4);` 但是不能把d拷贝给c +> +> 因为先构造了c,此时d还没有构造 + + + +### 8.new 和malloc的区别 + +```cpp +#include +using namespace std; + +class A { +public: + A() { + cout << "default constructor" << endl; + } + ~A() { + cout << "destructor" << endl; + } + +}; + +int main() { + int n = 10; + + cout << "malloc int : " << endl; + int *data = (int *)malloc(sizeof(int) * n); + free(data); + cout << "new int : " << endl; + int *data2 = new int[n]; // 用法简单 + delete[] data2; // delete连续的数组空间 + + cout << "malloc A : " << endl; + A *Adata1 = (A *)malloc(sizeof(A) * n); + for (int i = 0; i < n; i++) { // 为每一个变量赋值 + new(Adata1 + i) A();// 原地构造 new(首地址) A():要调用那个类型的构造 + // cin >> Adata1 + 1; + } + free(Adata1); + cout << "new A : " << endl; + A *Adata2 = new A[10]; + A *Adata3 = new A; + delete[] Adata2; + // delete[] Adata2; + delete Adata3; // 单一变量不需要加[] + + + + + return 0; +} + +``` + + + +```cpp +malloc int : +new int : +malloc A : +default constructor +default constructor +default constructor +default constructor +default constructor +default constructor +default constructor +default constructor +default constructor +default constructor +new A : +default constructor +default constructor +default constructor +default constructor +default constructor +default constructor +default constructor +default constructor +default constructor +default constructor +default constructor +destructor +destructor +destructor +destructor +destructor +destructor +destructor +destructor +destructor +destructor +destructor +``` + + + +> **new 和malloc的区别**: +> +> 1.new用法简单比较简单 +> +> 2.使用new的时候可以调用默认构造函数,malloc不会调用,不会初始化 +> +> 3.new是一个运算符,可以重载,malloc是一个函数 +> +> 4.delete[] 连续的数组空间,单一变量不需要加[] +> +> + + + +## 2.6 类属性和类方法 + +![截屏2021-03-07 下午7.08.27](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-03-07%20%E4%B8%8B%E5%8D%887.08.27.png) + + + +所有对象共有的属性和方法 + +## 2.7 const 方法 + + + +![截屏2021-03-07 下午7.12.55](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-03-07%20%E4%B8%8B%E5%8D%887.12.55.png) + + + + + +## 2.8对象与引用 + +引用在定义的时候需要初始化, + +避免出现b=a;是赋值还是引用的歧义 + +![截屏2021-03-07 下午7.12.50](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-03-07%20%E4%B8%8B%E5%8D%887.12.50.png) + +## 2.9 c++中的结构体和类 + + + +![截屏2021-03-07 下午7.20.43 1](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-03-07%20%E4%B8%8B%E5%8D%887.20.43%201.png) + +> struct在c++中的底层实现是类 和C++ class 是一样,与C不同,C是结构体 + + + +> struct 默认访问权限public 黑名单策略: +> +> class 默认访问权限为private 白名单策略:可以让外界想访问的东西定义在public内 + + + + + +> C++已经有class为什么还要保留struct关键字? + +兼容C, + + + + + +> 为什么struct默认访问权限不是private? + +如果设计成私有的,原来大量的C语言就无法使用了。struct的设计目的是让外部的程序访问其数据成员,class设计的目的之一是不让外部程序直接访问其数据成员。 + + + +struct更适合看成一个数据结构的实现,而class更适合看成一个对象的实现。 + + + + + +## 附1:返回值优化 + +```cpp +#include +using namespace std; + +class A { +public: + A() { + cout << "default constructor" << endl; + } + A(int x) : x(x) { + cout << "transform &x: " << &x << endl; + cout << "&this = " << &(*this) << endl; + cout << "transform constructot" << endl; + } + A(const A &a) { + cout << "copy constructor" << endl; + } + int x; +private: +}; + +A func() { + A temp(69); + cout << "func &temp: " << &temp << endl; + return temp; +} + +int main() { + A a = func(); // + cout << "main &a: " << &a << endl; + cout << a.x << endl; + + + return 0; +} +``` + +```cpp +transform &x: 0x7ffedfcaa824 +&this = 0x7ffedfcaa86c +transform constructot +func &temp: 0x7ffedfcaa86c +main &a: 0x7ffedfcaa86c +69 +``` + + + +> temp地址与a的地址是一样的,既然temp要赋值给a,编译期就会优化,直接将temp的地址 赋值给a + + + +![截屏2021-03-07 下午9.23.40](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-03-07%20%E4%B8%8B%E5%8D%889.23.40.png) + +![截屏2021-03-07 下午9.25.10](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-03-07%20%E4%B8%8B%E5%8D%889.25.10.png) + +![截屏2021-03-07 下午9.25.16](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-03-07%20%E4%B8%8B%E5%8D%889.25.16.png) + +![截屏2021-03-07 下午9.26.10](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-03-07%20%E4%B8%8B%E5%8D%889.26.10.png) + +![截屏2021-03-07 下午9.31.56](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-03-07%20%E4%B8%8B%E5%8D%889.31.56.png) + +![截屏2021-03-07 下午9.32.59](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-03-07%20%E4%B8%8B%E5%8D%889.32.59.png) + +> 返回值的第一次优化:优化掉了临时匿名变量,去掉了中间商,只做了一次拷贝优化 + + + +![截屏2021-03-07 下午9.52.15](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-03-07%20%E4%B8%8B%E5%8D%889.52.15.png) + + + + + +> 最终优化样式 + +关闭编译期优化 + + + +```cpp +g++ -fno-elide-constructors 17.RVO.cpp +``` + + + +```cpp +#include +using namespace std; + +class A { +public: + A() { + cout << this << " default constructor" << endl; + } + A(int x) : x(x) { + cout << this << " transform constructot" << endl; + } + A(const A &a) { + cout << this << " copy constructor" << endl; + } + int x; +private: +}; +A func() { + A temp(69); + cout << "func &temp: " << &temp << endl; + return temp; +} + +int main() { + A a = func(); // + cout << "main &a: " << &a << endl; + cout << a.x << endl; + + + return 0; +} +``` + + + +```cpp +0x7ffee232984c transform constructot // temp初始化 +func &temp: 0x7ffee232984c // temp地址 +0x7ffee232986c copy constructor // 第一次拷贝给临时匿名对象的变量 +0x7ffee2329868 copy constructor // 第二次拷贝给对象a +main &a: 0x7ffee2329868 +424476709 +``` + + + + + +> 实现代码后,不正确的代码调用了几次拷贝构造 +> +> 实现工程时不能随便改变拷贝构造的语义,因为只有你自己知道,编译器不知道,编译器会对拷贝构造优化 + + + +```cpp +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +using namespace std; + +class A { +public: + A() { + cout << this << " default constructor" << endl; + } + A(int x) : x(x) { + cout << this << " transform constructot" << endl; + } + A(const A &a) { + cout << this << " copy constructor" << endl; + } + A &operator=(const A &a) { + cout << this << " : operator=" << endl; + return *this; + } + int x; +private: +}; + +A func() { + A temp(69); + cout << "func &temp: " << &temp << endl; + return temp; +} + +int main() { + A a = func(); // + cout << "main &a: " << &a << endl; + cout << a.x << endl; + + cout << "+++++++========+++++++" << endl; + a = func(); + cout << a.x << endl; + + + return 0; +} +``` + +```cpp +0x7ffeef34e868 transform constructot +func &temp: 0x7ffeef34e868 +main &a: 0x7ffeef34e868 +69 ++++++++========+++++++ +0x7ffeef34e86c transform constructot +func &temp: 0x7ffeef34e86c +0x7ffeef34e868 : operator= +69 +``` + + + + + +> 第二种情况不构成优化的条件,所有会有两个地址 + + + +> 返回值优化,通过替换this指针进行优化 + + + +> 如果没有写拷贝构造函数,在编译期没有优化的情况下,他会默认调用构造函数 + + + + + +## const代码演示 + + + +```cpp +#include +using namespace std; + +class People { +public: + People() : say_cnt(0) { + People::total_num += 1; + } + static void say_count() { + cout << People::total_num << endl; + } + void say() const { // const类型必须调用const类型方法 + cout << "hahahah~, funny!" < const类型的方法就是给const类型的对象准备的 +> +> const类型必须调用const类型方法 +> +> mutable 实现逻辑意义上的const +> +> 声明,在类内加static,为了和其他变量区分 +> +> 类的变量的定义, 不需要加static关键字 +> +> const 类型对象不能调用非const类型的方法,因为可能会改变const类型的变量 + + + + + + + + + + + +> 如何不让一个类被拷贝 + +```cpp +#include +using namespace std; + +class A { +public: + //A() = delete; // 删除默认构造函数 + //A(const A &) = default; // 使用默认拷贝构造 没有任何功能上的意义, 可以尽量避免发生错误 + A() = default; +private: + A(const A &) = delete; + A &operator=(A &a); + const A &operator=(const A &a) const; +}; + + + +int main() { + A a; + A b; + //a = b; // 此处报错 + (a = 123) = 456; + cout << a.x << endl; + + return 0; +} +``` + + + +``` +456 +``` + + + +> `A() = delete;` // 删除默认构造函数 +> `A(const A &) = default;` // 使用默认拷贝构造 没有任何功能上的意义, 可以尽量避免发生错误 + + + +> 为什么返回引用? +> +> 可以连续使用运算符 + + + + + + + +## 2.10 重载 + +![截屏2021-03-08 下午5.42.40](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-03-08%20%E4%B8%8B%E5%8D%885.42.40.png) + +![截屏2021-03-08 下午5.43.26](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-03-08%20%E4%B8%8B%E5%8D%885.43.26.png) + + + +```cpp +#include +using namespace std; + + +// func(int) +// func(int, int) +int func(int x, int y = 2) { + return x * y; +} + +//double func(int x) {//无法通过返回值区分函数类型 +// return x * x + 5; +//} + + +// +double func(double x) { + return x * x; +} + +int main() { + + cout << func(2) << endl;; // func() 1 + cout << func(5.3) << endl; // func()2 + cout << func(2, 4) << endl; // func() 3 + + return 0; +} + +``` + + + + + +> 函数重载==名字相同==,==参数列表不同== +> +> ==与返回值无关== + + + + + +## 友元函数 + + + +> 友元函数是类外部的函数,但是通过申明友元函数可以实现访问类内部的private成员属性 + + + +```cpp +#include +using namespace std; + +class Data { // 数据类 +public: + Data() {} + Data(int x, int y) : x(x), y(y) { + cout << this << endl; + } + + friend ostream &operator<<(ostream &out, const Data &d); +private: + int x, y; +}; + +ostream &operator<<(ostream &out, const Data &d) { + out << "(" << d.x << " " << d.y << ")" << endl; +} + +int main() { + + Data d(10, 9); + cout << d << endl; + + return 0; + } +``` + + + + + +## 重载 + +![截屏2021-03-08 下午6.32.11](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-03-08%20%E4%B8%8B%E5%8D%886.32.11.png) + + + +> sizeof是运算符 + + + +```cpp +#include +using namespace std; + +class Point { +public: + Point(); + Point(int x, int y); + Point operator+(const Point &); // 类内重载 + Point &operator+=(int); // 类内重载 +private: + friend Point operator+(const Point &a, const Point &b);// 类外重载 + friend ostream &operator<<(ostream &out, const Point &a);// 类外重载 + int x; + int y; +}; + +Point::Point() : x(0), y(0) {} +Point::Point(int x, int y) : x(x), y(y) {} + +Point Point::operator+(const Point &a) { // 类内重载 + cout << "inner operator+" << endl; + Point c(this->x + a.x, this->y + a.y); + return c; +} + +Point &Point::operator+=(int n) { // 类内重载 + x += n, y += n; + return *this; +} + + +Point operator+(const Point &a, const Point &b) { // 类外重载 + cout << "outer operator" << endl; + Point c(a.x + b.x, a.y + b.y); + return c; +} + +ostream &operator<<(ostream &out, const Point &a) {// 类外重载 + out << "(" << a.x << "," << a.y << ")" << endl; + return out; +} + +int main() { + Point a(3, 4); + Point b(7, 9); + Point c = a + b; + cout << a << endl; + cout << b << endl; + cout << c << endl; + + a += 2; + cout << a << endl; + + + return 0; +} + +``` + + + +```cpp +inner operator+ +(3,4) + +(7,9) + +(10,13) + +(5,6) +``` + + + + + +> +=为什么返回引用? + +可以实现连续+=的现象 + + + + + +## 重载成员函数原则 + +赋值(=),下标([]), 调用(()),成员访问(<>)运算符必须重载为成员函数 + +复合赋值运算符一般重载为成员函数 + +改变对象运算状态额运算符和类型密切相关的运算符,一般重组在为成员函数,如自增等 + +具有对称性的运算符可能转换任意一端的对象,通常重载为非成员函数,如相等,关系和位运算 + + + +[]数组对象()函数对象->指针对象:只支持类内重载 + +()函数对象:表现的像函数,其实是一个对象,本质上是一个像函数一样被调用的对象 + +->指针对象:表现的像个指针,实际上是一个对象 + + + +```cpp +#include +using namespace std; + +class Point { +public : + Point(); + Point(int x, int y); + Point operator+(const Point &); // 类内重载 + Point &operator+=(int); // 类内重载 + int operator[](string s); + int getX() { return x; } + int getY() { return y; } +private : + friend Point operator+(const Point &a, const Point &b); + friend ostream &operator<<(ostream &out, const Point &a); + int x; + int y; +}; +class PPoint { // 指向Point的指针 +public : + PPoint(Point *p) : p(p) {} + Point *operator->() { + return p; + } +private : + Point *p; +}; + +class ADD { +public : + ADD(int c) : c(c){} + int operator()(int a, int b) { + return a + b + c; + } +private : + int c; +}; + + + + + +Point::Point() : x(0), y(0) {} +Point::Point(int x, int y) : x(x), y(y) {} + +int Point::operator[](string s) { + if (s == "x") return x; + if (s == "y") return y; + return 0; +} + +//string operator()(string s) { +// return s; +//} + + + +Point Point::operator+(const Point &a) { // 类内重载 + cout << "inner operator+" << endl; + Point c(this->x + a.x, this->y + a.y); + return c; +} + +Point &Point::operator+=(int n) { // 类内重载 + x += n, y += n; + return *this; +} + + +Point operator+(const Point &a, const Point &b) { // 类内重载 + cout << "outer operator" << endl; + Point c(a.x + b.x, a.y + b.y); + return c; +} + +ostream &operator<<(ostream &out, const Point &a) { + out << "(" << a.x << "," << a.y << ")" << endl; + return out; +} + +int main() { + + + ADD add(5); + cout << add(6, 7) << endl; + + + + Point a(3, 4); + Point b(7, 9); + Point c = a + b; + + + + cout << a["x"] << endl; + cout << a["y"] << endl; + //return 0; + + + cout << a << endl; + cout << b << endl; + cout << c << endl; + + a += 2; + cout << a << endl; + + PPoint p = &a; + cout << p->getX() << " " << p->getY() << endl; + + return 0; +} +``` + + + + + + + + + + + +## 通过重载实现简单的智能指针 + +```cpp +#include +#include +using namespace std; + +namespace haizei { + +class A { +public : + A() { + cout << "dfault constructor" << endl; + } + int x, y; + ~A() { + cout << "destructor" << endl; + } +}; + +class my_shared_ptr { +public : + my_shared_ptr(); + my_shared_ptr(A *); + my_shared_ptr(const my_shared_ptr &); + int use_count(); + A *operator->(); + A &operator*(); + my_shared_ptr &operator=(const my_shared_ptr &); + ~my_shared_ptr(); +private : + void decrease_by_one(); + void increase_by_one(); + int *cnt; + A *obj; +}; +my_shared_ptr::my_shared_ptr() : obj(nullptr), cnt(nullptr) {} +my_shared_ptr::my_shared_ptr(A *obj) : obj(obj), cnt(new int(1)) {} + +my_shared_ptr::my_shared_ptr(const my_shared_ptr &p) : obj(p.obj), cnt(p.cnt) { + increase_by_one(); //*p.cnt += 1; +} + +int my_shared_ptr::use_count(){ + return cnt ? *cnt : 0; +} + +A *my_shared_ptr::operator->() { + return obj; +} + +A &my_shared_ptr::operator*() { + return *obj; +} +my_shared_ptr::~my_shared_ptr() { + this->decrease_by_one(); + this->obj = nullptr; + this->cnt = nullptr; +} + +void my_shared_ptr::decrease_by_one() { //引用计数-1 + if (this->cnt != nullptr) { + *(this->cnt) -= 1; + if (*(this->cnt) == 0) { + delete this->obj; + delete this->cnt; + } + } + return ; +} +void my_shared_ptr::increase_by_one() { //引用计数+1 + if (this->cnt != nullptr) { + *(this->cnt) += 1; + } + return ; +} + + + +my_shared_ptr &my_shared_ptr::operator=(const my_shared_ptr &p) { + if (this->obj != p.obj) { + decrease_by_one(); + this->obj = p.obj; + this->cnt = p.cnt; + increase_by_one(); + } + return *this; +} + +} // end of haizei + +using namespace haizei; +int main() { + + cout << "p1" << endl; + A *p1 = new A(); + p1 = nullptr; + + cout << endl << "p2" << endl; + my_shared_ptr p2(new A()); + cout << p2.use_count() << endl; // 1 + my_shared_ptr p3 = p2; + p2->x = 123; + p2->y = 456; + (*p2).x = 456; + cout << p3.use_count() << endl; // 2 + p2 = nullptr; // 自动析构 + cout << p3.use_count() << endl; // 1, p2指向了其他对象 + + p2 = p3; + cout << p2.use_count() << endl; + + + + + return 0; +} +``` + + + + + +## 2.11 sort简单实现 + +```cpp +#include +#include +#include +using namespace std; + +ostream &operator<<(ostream &out, const vector &a) { + for (auto x : a) { + out << x << " "; + } + return out; +} + +bool cmp(int a, int b) { + //return a < b;// a排在b前面:a < b,从小到大 + return a > b;// a排在b后面:a > b,从大到小 +} + +class CMP { +public : + CMP(int z = 0) : z(z) { // z == 0 less z = 1 greater + + } + bool operator()(int a, int b) { + // return a > b; + return (a < b) ^ !!(z); + } + int z; +}; + +int main() { + vector arr; + int n = 9; + //cin >> n; + srand(time(0)); + for (int i = 0; i < n; i++) { + int temp = rand() % 100; + arr.push_back(temp); + } + + //sort(arr.begin(), arr.end(), cmp; // cmp函数 + //sort(arr.begin(), arr.end(), CMP()); // cmp匿名对象, 仿函数 + CMP cmp2; + //sort(arr.begin(), arr.end(), cmp2); // cmp2对象 相对于函数仿函数功能更加强大 + //sort(arr.begin(), arr.end(), CMP()); // cmp匿名对象, 默认从小到大排序 + sort(arr.begin(), arr.end(), CMP(1)); // cmp匿名对象, 传参1 从 大到小排序 外在表现的像一个函数 实际上比函数更加强大 + cout << arr << endl; + + + + return 0; +} +``` + + + + + +```c++ +#include +#include +#include +#include +using namespace std; + +ostream &operator<<(ostream &out, const vector &a) { + for (auto x : a) { + out << x << " "; + } + return out; +} + +bool cmp(int a, int b) { + //return a < b;// a排在b前面:a < b,从小到大 + return a > b;// a排在b后面:a > b,从大到小 +} + + + class CMP { + public : + CMP(int z = 0) : z(z) { // z == 0 less z = 1 greater + + } + bool operator()(int a, int b) { + // return a > b; + return (a < b) ^ !!(z); + } + int z; + }; + +namespace haizei { +class CMP1 { +public : + CMP1(int z = 0) : z(z) { // z == 0 less z = 1 greater + + } + bool operator()(int a, int b) { + // return a > b; + return (a < b) ^ !!(z); + } + int z; +}; +void sort(int *arr, int l, int r, function cmp = CMP1()) { // /快速排序 + if (l >= r) return ; + int x = l, y = r, z = arr[(l + r) >> 1]; + do { + while (cmp(arr[x], z)) ++x; + while (cmp(z, arr[y])) --y; + if (x <= y) { + swap(arr[x], arr[y]); + ++x, --y; + } + } while (x <= y); + sort(arr, l, y, cmp); + sort(arr, x, r, cmp); + return ; +} +} // end of haizei + +int main() { + vector arr; + int n = 9; + //cin >> n; + srand(time(0)); + for (int i = 0; i < n; i++) { + int temp = rand() % 100; + arr.push_back(temp); + } + + //sort(arr.begin(), arr.end(), cmp; // cmp函数 + //sort(arr.begin(), arr.end(), CMP()); // cmp匿名对象, 仿函数 + CMP cmp2; + //sort(arr.begin(), arr.end(), cmp2); // cmp2对象 相对于函数仿函数功能更加强大 + //sort(arr.begin(), arr.end(), CMP()); // cmp匿名对象, 默认从小到大排序 + //sort(arr.begin(), arr.end(), CMP(1)); // cmp匿名对象, 传参1 从 大到小排序 外在表现的像一个函数 实际上比函数更加强大 + + //cout << arr << endl; + int arr2[5] = {6, 8, 4, 5, 1}; + haizei::sort(arr2, 0, 4); // 默认从小到大排序 + cout << "默认从小到大:" << endl; + for (int i = 0; i < 5; i++) { + cout << arr2[i] << " "; + } + cout << endl; + + cout << "cmp 从大到小: " << endl; + haizei::sort(arr2, 0, 4, cmp); // cpm从大到小排序 + for (int i = 0; i < 5; i++) { + cout << arr2[i] << " "; + } + cout << endl; + + cout << "默认 从小到大: " << endl; + haizei::sort(arr2, 0, 4); // 默认从小到大排序 + for (int i = 0; i < 5; i++) { + cout << arr2[i] << " "; + } + cout << endl; + + cout << "仿函数 从大到小: " << endl; + haizei::sort(arr2, 0, 4, haizei::CMP1(1)); // 仿函数CMP从大到小排序 + for (int i = 0; i < 5; i++) { + cout << arr2[i] << " "; + } + cout << endl; + + + + + return 0; +} +``` + + + + + + + +## 其他重载知识点 + +不能重载的5个运算符 + + + +```cpp +. 成员引用运算符 +.* 成员指针引用运算符 +sizeof 运算符 +?:唯一三目运算符 +::作用域操作符 +``` + + + +只能在类内重载的4个操作符 + +```cpp +()函数括号运算符 函数对象 +[]数组方括号运算符 数组对象 +->间接引用运算符 指针对象 +=赋值运算符 +``` + + + + + + + + + +# 3.继承 + + + +## 3.1什么是继承 + + + +```cpp +class Animal { +public : + string name() { + return this->__name; + } +private : + string __name; +}; + +class Cat : public Animal { + +}; +``` + + + +> 1.子类继承父类所有的属性和方法 + + + +> 2.拥有不代表能访问 + + + + + +## 3.2 继承-子类的访问权限 + +![截屏2021-03-25 下午10.03.40](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-03-25%20%E4%B8%8B%E5%8D%8810.03.40.png) + + + +子类是父类的类外部 + + + +> 子类只能访问父类中的public和protected + + + +> 1.继承权限不影响子类对父类内容的访问 +> +> 2.继承影响类外部访问子类内部继承至父类这部分内容的访问权限 + + + +![截屏2021-03-25 下午10.10.24](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-03-25%20%E4%B8%8B%E5%8D%8810.10.24.png) + + + + + + + +```cpp +#include +using namespace std; + +class Animal { +public : + Animal(string name = "加菲猫") : name(name) {} + void say() { + cout << "my name is " << name << endl; + } +private : + string name; +}; + +class Cat : public Animal {}; + +int main() { + cout << sizeof(Animal) << " " << sizeof(Cat) << endl; + // 32 32 ==> Cat 继承了 Animal 的内容, 大小一样 + Cat c; + c.say(); + + return 0; +} + +``` + +```cpp +32 32 +my name is 加菲猫 +``` + + + +## 3.3继承代码演示 + +```cpp +#include +using namespace std; + +class Animal { +public : + Animal(string name = "加菲猫") : name(name) {} + void say() { + cout << "my name is " << name << endl; + name2 = "加菲猫"; + } + void rename(string name) { + this->name = name; + return ; + } +private : + string name; +protected : + string name2; +}; + +class Cat : public Animal { +public : + void say1() { + //cout << "加菲猫, my name is " << name << endl; // std::string Animal::name' is private within this context + cout << "加菲猫, my name is " << name2 << endl; // 子类可以访问父类的pritected + } +}; + +class Tigger : public Cat { +public : + void say2() { + // cout << "houhouhou, my name is " << name2 << endl; // 无法访问private继承 private Animal + // 可以访问protected继承,和public继承 + // + } +}; + +int main() { + cout << sizeof(Animal) << " " << sizeof(Cat) << endl; // 32 32 ==> Cat 继承了 Animal 的内容, 大小一样 + Cat c; + //c.say(); // Cat继承了Animal的内容 public Animal + c.say1(); + + Animal *p = &c; + // 通过父类的指针访问子类继承父类的内容,前提条件是继承方法必须是public继承, + //如果Cat是private和protected继承,则无法访问 + p->say(); + + + return 0; +} +``` + + + + + +## 3.4继承-构造函数 + +> 子类中有父类的数据区, 先构造父类的数据区,再构造子类的数据区 + +> 构造完成顺序: 父类、子类 + +> 析构顺序: 子类、父类 + + + +```cpp +#include +using namespace std; + +class Animal { +public : + Animal(string name = "加菲猫") : name(name) { + cout << "Animal default constructor" << endl; + } + void say() { + cout << "my name is " << name << endl; + name2 = "加菲猫"; + } + void rename(string name) { + this->name = name; + return ; + } + ~Animal() { + cout << "Animel destructor" << endl; + } +private : + string name; +protected : + string name2; +}; + +class Cat : public Animal { +public : + Cat() : Animal("Cat MIMI") { + cout << "Cat default structor" << endl; + } + void say1() { + //cout << "加菲猫, my name is " << name << endl; // std::string Animal::name' is private within this context + cout << "加菲猫, my name is " << name2 << endl; // 子类可以访问父类的pritected + } + ~Cat() { + cout << "Cat deconstructor" << endl; + } +}; + + +int main() { + cout << sizeof(Animal) << " " << sizeof(Cat) << endl; // 32 32 ==> Cat 继承了 Animal 的内容, 大小一样 + Cat c; + //c.say(); // Cat继承了Animal的内容 public Animal + c.say1(); + + Animal *p = &c; // 通过父类的指针访问子类继承父类的内容,前提条件是继承方法必须是public继承, 如果Cat是private和protected继承,则无法访问 + p->say(); + + + return 0; +} +``` + + + +```cpp +64 64 +Animal default constructor +Cat default structor +加菲猫, my name is +my name is Cat MIMI +Cat deconstructor +Animel destructor +``` + + + +## 3.5菱形继承 + + + +![截屏2021-03-25 下午11.04.00](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-03-25%20%E4%B8%8B%E5%8D%8811.04.00.png) + + + + + +```cpp +#include +using namespace std; + +class A { +public : + A() : x(123) {} + int x; +}; + +class B : public A { +public : + void setX(int x) { + cout << "setX : &x = " << &(this->x) << endl; + this->x = x; + return ; + } +}; + +class C : public A { +public : + int getX() { + cout << "getX : &x = " << &(this->x) << endl; + return x; + } +}; + +class D : public B, public C { +}; + +int main() { + D d; + cout << "&d" << &d << endl; + cout << "get X :" << d.getX() << endl; + d.setX(12345); + cout << "get X :" << d.getX() << endl; + + return 0; +} + +``` + +```cpp +get X :getX : &x = 0x7ffee8c60854 +123 +setX : &x = 0x7ffee8c60850 +get X :getX : &x = 0x7ffee8c60854 + 123 +``` + +![截屏2021-03-25 下午11.25.36](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-03-25%20%E4%B8%8B%E5%8D%8811.25.36.png) + +>setX调用的是继承至B内的x +> +>getY调用的是继承至C内的x + + + +C# java:单继承但是可以继承多个接口 + + + +![截屏2021-03-25 下午11.42.28](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-03-25%20%E4%B8%8B%E5%8D%8811.42.28.png) + + + + + +## 3.6继承-拷贝构造&赋值运算符 + +![截屏2021-03-25 下午11.43.19](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-03-25%20%E4%B8%8B%E5%8D%8811.43.19.png) + + + +```cpp +#include +using namespace std; + +class Animal { +public : + Animal(string name = "加菲猫") : name(name) { + cout << "Animal default constructor" << endl; + } + void say() { + cout << "my name is " << name << endl; + name2 = "加菲猫"; + } + void rename(string name) { + this->name = name; + return ; + } + ~Animal() { + cout << "Animel destructor" << endl; + } +protected : + string name; +protected : + string name2; +}; + +class Cat : public Animal { +public : + Cat() : Animal("Cat MIMI") { + cout << "Cat default structor" << endl; + } + Cat(const Cat &c) { //拷贝构造 + cout << "Cat copy constructor" << endl; + } + void say1() { + cout << "加菲猫, my name is " << name << endl; // std::string Animal::name' is private within this context + //cout << "加菲猫, my name is " << name2 << endl; // 子类可以访问父类的pritected + } + ~Cat() { + cout << "Cat deconstructor" << endl; + } +}; + + +int main() { + Cat c; + Cat a = c; // c拷贝给a + a.say1(); + //c.say(); // Cat继承了Animal的内容 public Animal + c.say1(); + + return 0; +} +``` + + + +```cpp + Animal default constructor +Cat default structor +Animal default constructor +Cat copy constructor +加菲猫, my name is 加菲猫 // 此处调用的是父类的默认构造,说明父类没正确的拷贝 +加菲猫, my name is Cat MIMI +Cat deconstructor +Animel destructor +Cat deconstructor +Animel destructor +``` + + + + + +> 正确的拷贝 + +```cpp +#include +using namespace std; + +class Animal { +public : + Animal(string name = "加菲猫") : name(name) { + cout << "Animal default constructor" << endl; + } + Animal(const Animal &a) : name(a.name) { //增加父类的拷贝构造 + cout << "Animal copy constructor" << endl; + } + void say() { + cout << "my name is " << name << endl; + name2 = "加菲猫"; + } + void rename(string name) { + this->name = name; + return ; + } + ~Animal() { + cout << "Animel destructor" << endl; + } +protected : + string name; +protected : + string name2; +}; + +class Cat : public Animal { +public : + Cat() : Animal("Cat MIMI") { + cout << "Cat default structor" << endl; + } + Cat(const Cat &c) : Animal(c) { // 在子类中显示的调用父类的拷贝函数 + cout << "Cat copy constructor" << endl; + } + void say1() { + cout << "加菲猫, my name is " << name << endl; // std::string Animal::name' is private within this context + //cout << "加菲猫, my name is " << name2 << endl; // 子类可以访问父类的pritected + } + ~Cat() { + cout << "Cat deconstructor" << endl; + } +}; + + +int main() { + Cat c; + Cat a = c; // c拷贝给a + a.say1(); + c.say1(); + + return 0; +} +``` + + + +```cpp +Animal default constructor +Cat default structor +Animal copy constructor +Cat copy constructor +加菲猫, my name is Cat MIMI +加菲猫, my name is Cat MIMI +Cat deconstructor +Animel destructor +Cat deconstructor +Animel destructor +``` + + + +## 3.7继承-拷贝应用 + + + +> 实际开发中推荐继承一个真实基类,多个功能性基类 + + + +```cpp +#include +using namespace std; + +class Uncopyable { // 只要基类不可以被拷贝,子类一定不能被拷贝 +public : + Uncopyable() = default; + Uncopyable(const Uncopyable &) = delete; + Uncopyable &operator=(const Uncopyable &) = delete; +}; + + +class A : public Uncopyable { +public : + //A(const A &) = delete; // 不允许被拷贝 +}; + +class B : public Uncopyable { +public : + //B(const B &) = delete; +}; + +class C : public Uncopyable { +public : + //C(const B &) = delete; +}; + + +int main() { + A a; + C c = a; // conversion from 'A' to non-scalar type 'C' requested + + return 0; +} +``` + + + + + +## 赋值运算符继承代码演示 + +```cpp +#include +using namespace std; + +class Animal { +public : + Animal(string name = "加菲猫") : name(name) { + cout << "Animal default constructor" << endl; + } + Animal(const Animal &a) : name(a.name) { + cout << "Animal copy constructor" << endl; + } + Animal &operator=(const Animal &a) { + this->name = a.name; + return *this; + } + void say() { + cout << "my name is " << name << endl; + name2 = "加菲猫"; + } + void rename(string name) { + this->name = name; + return ; + } + ~Animal() { + cout << "Animel destructor" << endl; + } +protected : + string name; +protected : + string name2; +}; + +class Cat : public Animal { +public : + Cat() : Animal("Cat MIMI") { + cout << "Cat default structor" << endl; + } + Cat(const Cat &c) : Animal(c) { // 在子类中显示的调用父类的构造函数 + cout << "Cat copy constructor" << endl; + } + Cat &operator=(const Cat &c) { + this->Animal::operator=(c); + return *this; + } + void say1() { + cout << "加菲猫, my name is " << name << endl; // std::string Animal::name' is private within this context + //cout << "加菲猫, my name is " << name2 << endl; // 子类可以访问父类的pritected + } + ~Cat() { + cout << "Cat deconstructor" << endl; + } +}; + +class Tigger : public Cat { +public : + void say2() { + // cout << "houhouhou, my name is " << name2 << endl; // 无法访问private继承 private Animal + // 可以访问protected继承,和public继承 + // + } +}; + +int main() { + //cout << sizeof(Animal) << " " << sizeof(Cat) << endl; // 32 32 ==> Cat 继承了 Animal 的内容, 大小一样 + Cat c; + Cat a = c; // c拷贝给a + cout << "a.say1()" << endl; + a.say1(); + //c.say(); // Cat继承了Animal的内容 public Animal + cout << "c.say1()" << endl; + c.say1(); + + //Animal *p = &c; // 通过父类的指针访问子类继承父类的内容,前提条件是继承方法必须是public继承, 如果Cat是private和protected继承,则无法访问 + //p->say(); + + + return 0; +} +``` + + + + + +## 3.8 菱形继承解决方案-virtual + +```cpp +#include +using namespace std; + +class A { +public : + A() : x(123) {} + int x; +}; + +class B : virtual public A { // 虚继承,子类会将虚继承的部分(来自同一个类)合并 +public : + void setX(int x) { + //cout << "setX : &x = " << &(this->x) << endl; + this->x = x; + return ; + } +}; + +class C : virtual public A { +public : + int getX() { + //cout << "getX : &x = " << &(this->x) << endl; + return x; + } +}; + +class D : public B, public C { +}; + +int main() { + D d; + cout << "&d" << &d << endl; + cout << "get X :" << d.getX() << endl; + d.setX(12345); + cout << "get X :" << d.getX() << endl; + + + return 0; +} +``` + + + +```cpp +&d0x7ffee3bac820 +get X :123 +get X :12345 +``` + + + +> `virtual`: 虚继承,子类会将父类中虚继承的部分合并成一个部分 + + + +# 4.多态 + +## 4.1多态 + +从继承—到多态 + +```cpp +#include +using namespace std; + +class Animal { +public : + Animal(string name) : name(name) {} + void run() { + cout << "I don't know how to run" << endl; + } +private : + string name; +}; + +class Cat : public Animal { +public : + Cat(string name) : Animal(name) {} + void run() { + cout << "I can run with four legs" << endl; + } +}; + + +int main() { + Cat a("Tom"); + Animal &b = a; + Animal *c = &a; + + a.run(); // 猫跑 + b.run(); // animal run + c->run(); // animal run + + + return 0; +} +``` + +```cpp +I can run with four legs +I don't know how to run +I don't know how to run +``` + + + +> 普通成员方法: +> +> 跟着类走(看调用的时候被谁调用)对象是什么类型的,就调用该类的方法) +> +> 在编译期就已确定 + + + +> 虚函数virtual +> +> 虚函数跟着对象走 +> +> b是Cat类型对象的引用,所以调用的是cat类型的方法 +> +> c是指向Cat类型对象的指针,所以调用的是cat类型的方法 + + + +override说明性关键字,覆盖父类中同名的关键字 + +```cpp +#include +using namespace std; + +class Animal { +public : + Animal(string name) : name(name) {} + virtual void run() { + cout << "I don't know how to run" << endl; + } +private : + string name; +}; + +class Cat : public Animal { +public : + Cat(string name) : Animal(name) {} + void run() override { + cout << "I can run with four legs" << endl; + } +}; + + +int main() { + Cat a("Tom"); + Animal &b = a; + Animal *c = &a; + + a.run(); // 猫跑 + b.run(); // 猫跑 + c->run(); // 猫跑 + + + return 0; +} +``` + + + +```cpp +I can run with four legs +I can run with four legs +I can run with four legs +``` + + + + + +## 4.2virtual作用1:多外显示接口统一 + + + +```cpp +#include +using namespace std; + +class Animal { +public : + Animal(string name) : name(name) {} + virtual void run() { + cout << "I don't know how to run" << endl; + } +private : + string name; +}; + +class Cat : public Animal { +public : + Cat(string name) : Animal(name) {} + void run() override { + cout << "I can run with four legs" << endl; + } +}; + +class People : public Animal { +public : + People(string name) : Animal(name) {} + void run() { + cout << "I can run with two legs!" << endl; + } +}; + +class Bat : public Animal { +public : + Bat(string name) : Animal(name) {} + void run() { + cout << "I can fly!" << endl; + } +}; + + + +int main() { + + srand(time(0)); + + #define MAX_N 10 + Animal **zoo = new Animal*[MAX_N]; + for (int i = 0; i < MAX_N; i++) { + switch (rand() % 3) { + case 0: zoo[i] = new Cat("cat"); break; + case 1: zoo[i] = new People("people"); break; + case 2: zoo[i] = new Bat("bat"); break; + } + } + + for (int i = 0; i < MAX_N; i++) { + zoo[i]->run(); + } + + return 0; +} +``` + + + +```cpp +I can fly! +I can run with two legs! +I can run with four legs +I can fly! +I can fly! +I can run with two legs! +I can fly! +I can fly! +I can fly! +I can run with four legs +``` + + + +> 每次出现的结果都不一样,虚函数不确定性指向哪个函数(cat->run, peopel->run,bat->run)只有程序运行的时候才能确定指向哪个对象的函数 +> +> 普通函数 跟着类走 在编译期就可以确定 +> +> 虚函数 跟着对象走 在程序运行期才可以确定 + + + +## 4.3 override作用和final关键字应用场景 + + + +```cpp +#include +using namespace std; + +class Animal { +public : + Animal(string name) : name(name) {} + virtual void run() { + cout << "I don't know how to run" << endl; + } + virtual void getName() final {} // 应用场景1:不想被子类修改的内容 + virtual void fl1() {} +private : + string name; +}; + +class Cat : public Animal { +public : + Cat(string name) : Animal(name) {} + void run() override final { + cout << "I can run with four legs" << endl; + } + //void fll() {} // 想重写fl1(),实际上函数名字打错了,但是编译器不会报错 + //void fll() override {} // 但是如果加上override就会报错, 提示函数不存在,主要起提示作用,完成语义的严谨性,减少bug +}; + +class Tigger : public Cat { +public : + //void run() {} // 子类无法重载父类的final, final终结虚函数 + // final禁止重写 +}; + + + +class People : public Animal { +public : + People(string name) : Animal(name) {} + void run() { + cout << "I can run with two legs!" << endl; + } +}; + +class Bat : public Animal { +public : + Bat(string name) : Animal(name) {} + void run() { + cout << "I can fly!" << endl; + } +}; + + + +int main() { + + srand(time(0)); + + #define MAX_N 10 + Animal **zoo = new Animal*[MAX_N]; + for (int i = 0; i < MAX_N; i++) { + switch (rand() % 3) { + case 0: zoo[i] = new Cat("cat"); break; + case 1: zoo[i] = new People("people"); break; + case 2: zoo[i] = new Bat("bat"); break; + } + } + + for (int i = 0; i < MAX_N; i++) { + zoo[i]->run(); + } + + return 0; +} +``` + + + +> override的作用? + +>子类 想重写fl1(),实际上函数名字打错了,但是编译器不会报错 +> 但是如果加上override就会报错, 提示函数不存在,主要起提示作用,完成语义的严谨性,减少bug + + + +> final关键字的作用和应用场景 + +> final功能:子类无法重载父类的final, final终结虚函数,final禁止重写 + +> 应用场景1:不想被子类修改的内容 + + + +## 虚函数为什么能跟着对象走? + + + +> 当有了虚函数后,编译期会改造虚函数的存储区 +> +> 头8个字节指向虚函数表vtable首地址 +> +> 虚函数表中存储虚函数 + +编译器根据类管理虚函数表 + + + + + +```cpp +#include +using namespace std; + +class A { +public : + virtual void say() { + cout << "class A say" << endl; + } + virtual void run() { + cout << "class A run" << endl; + } +}; + +class B : public A { +public : + void say() override { + cout << "class B say" << endl; + } +}; + +class C : public A { +public : + void run() override { + cout << "class C run" << endl; + } +}; + + +#define TEST(a) test(a, #a) +void test(A &a, string class_name) { + cout << "Object " << class_name << endl; + a.say(); + a.run(); + cout << "=========" << endl << endl; + return ; +} + + + +int main() { + + A a; + B b; + C c; + TEST(a); + TEST(b); + TEST(c); + + + + return 0; +} +``` + + + + + +```cpp +Object a +class A say +class A run +========= + +Object b +class B say +class A run +========= + +Object c +class A say +class C run +====== +``` + + + + + + + + + +![截屏2021-03-26 下午7.27.35](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-03-26%20%E4%B8%8B%E5%8D%887.27.35.png) + + + + + + + +> 如果将b的虚函数指针指向c的虚函数表,会发生什么? + + + + + +```cpp +#include +using namespace std; + +class A { +public : + virtual void say() { + cout << "class A say" << endl; + } + virtual void run() { + cout << "class A run" << endl; + } +}; + +class B : public A { +public : + void say() override { + cout << "class B say" << endl; + } +}; + +class C : public A { +public : + void run() override { + cout << "class C run" << endl; + } +}; + + +#define TEST(a) test(a, #a) +void test(A &a, string class_name) { + cout << "Object " << class_name << endl; + a.say(); + a.run(); + cout << "=========" << endl << endl; + return ; +} + + + +int main() { + + A a; + B b; + C c; + TEST(a); + TEST(b); + TEST(c); + ((void **)(&b))[0] = ((void **)(&c))[0]; + TEST(b); + + + + + return 0; +} + +``` + +```cpp +Object a +class A say +class A run +========= + +Object b +class B say +class A run +========= + +Object c +class A say +class C run +========= + +Object b +class A say +class C run +========= +``` + + + +> 虚函数表真的存在 + + + +this指针是成员方法隐藏的参数 + + + +> 普通指针为什么不能存储成员方法地址? + +普通的函数指针没有this指针,所以有了成员方法指针 + + + + + + + +```cpp +#include +using namespace std; + +class A { +public : + A() { x= 200; y = 400; } + virtual void say(int x) { + cout << "this->x : " << this->x << endl; + cout << "class A say : " << x << endl; + } + virtual void run() { + cout << "class A run" << endl; + } + void reg1() { + cout << "reg1 function" << endl; + } + void reg2() { + cout << "reg2 function" << endl; + } + void reg3() { + cout << "reg3 function" << endl; + } + void reg4() { + cout << "reg4 function" << endl; + } + void reg5() { + cout << "reg5 function" << endl; + } + int x, y; +}; + +class B : public A { +public : + B() { x = 300; } + void say(int x) override { + cout << "class B say : " << x << endl; + } +}; + +class C : public A { +public : + C() { x = 400; } + void run() override { + cout << "class C run" << endl; + } +}; + + +#define TEST(a) test(a, #a) +void test(A &a, string class_name) { + cout << "Object " << class_name << endl; + a.say(123); + a.run(); + cout << "=========" << endl << endl; + return ; +} + +typedef void (*func)(void *,int); + + + +int main() { + + A a; + B b; + C c; + TEST(a); // A say A run + TEST(b); // B say A run + TEST(c); // A say C run + ((void **)(&b))[0] = ((void **)(&c))[0]; // 将b的虚函数指针指向c的虚函数表 // A say C run + TEST(b); + + ((func **)(&b))[0][0](&b, 100); // b函数指向c的say(A say)方法 // A say (this->x : 300) + // &b this指针, this指针是成员方法隐藏的参数 + // 为什么是300 + // 传入的&b即传入的是b对象的this指针的地址,所以值的b的this指针的值 + cout << "=========" << endl << endl; + ((func **)(&b))[0][0](&c, 100); // this->x = 400 + // ==> + // + + // 成员方法指针 + void (A::*p)(int); + p = &A::say; + (a.*p)(12345); + + + void (A::*q[3])(); // 成员方法指针 + + + + q[0] = &A::reg1; + q[1] = &A::reg2; + q[2] = &A::reg3; + for (int i = 0; i < 10; i++) { + (a.*q[rand() % 3])(); + } + + + cout << "=++++ a.x" << endl; + int A::*d; // 成员属性指针 + d = &A::x; + cout << (a.*d) << endl; // a.x + cout << a.x << endl; + + cout << " a.y" << endl; + d = &A::y; + cout << (a.*d) << endl; // a.x + cout << a.y << endl; + cout << " +++++" << endl; + + + + + + + return 0; +} +``` + + + + + +## dynamic_cast + +```cpp +#include +using namespace std; + +class A { +public : + virtual void say() { + cout << "Class A" << endl; + } +}; + +class B : public A{ +public : + void say() override { + cout << "Class A" << endl; + } +}; + +class C : public A{ +public : + void say() override { + cout << "Class A" << endl; + } +}; + +int main() { + srand(time(0)); + A *p; + + switch (rand() % 2) { + case 0: + p = new B(); break; + case 1: + p = new C(); break; + } + + // p -> B ? C ? + cout << dynamic_cast(p) << endl; // dynamic_cast 将p地址尝试转换成目标地址B + cout << dynamic_cast(p) << endl; // 如果不成功返回0 + + + + + return 0; +} +``` + +``` +0 +0x7f910dc05ac0 +``` + + + +dynamic_cast会根据虚函数表转换成相应的地址 + + + +> 想要使用dynamic_cast,但是自己设计的类没有virtual, 怎么办? + +将析构函数设计成virtual + +```cpp +#include +using namespace std; + +class A { +public : + A() { + cout << "A constructor" << endl; + } + //virtual void say() { + // cout << "Class A" << endl; + //} + virtual ~A() { + cout << "A deconstructor" << endl; + } +}; + +class B : public A{ +public : + B() { + cout << "B constructor" << endl; + } + + //void say() override { + // cout << "Class B" << endl; + //} + virtual ~B() { + cout << "B deconstructor" << endl; + } + +}; + +class C : public A{ +public : + C() { + cout << "C constructor" << endl; + } + + //void say() override { + // cout << "Class C" << endl; + //} + virtual ~C() { + cout << "C deconstructor" << endl; + } + +}; + +int main() { + srand(time(0)); + A *p; + + switch (rand() % 2) { + case 0: + p = new B(); break; + case 1: + p = new C(); break; + } + delete p; // p是A类,是普通的成员方法,只会析构A的成员方法, + // 这时一定要将析构函数设计成虚构 + // p -> B ? C ? + + return 0; +} +``` + + + +```cpp +A constructor +C constructor +C deconstructor +A deconstructor +``` + + + + + +> 证明dynamic_cast和虚函数表的关系 + +```cpp +#include +using namespace std; + +class A { +public : + A() { + cout << "A constructor" << endl; + } + //virtual void say() { + // cout << "Class A" << endl; + //} + virtual ~A() { + cout << "A deconstructor" << endl; + } +}; + +class B : public A{ +public : + B() { + cout << "B constructor" << endl; + } + + //void say() override { + // cout << "Class B" << endl; + //} + virtual ~B() { + cout << "B deconstructor" << endl; + } + +}; + +class C : public A{ +public : + C() { + cout << "C constructor" << endl; + } + + //void say() override { + // cout << "Class C" << endl; + //} + virtual ~C() { + cout << "C deconstructor" << endl; + } + +}; + +void judge(A *p) { + if (dynamic_cast(p)) {// dynamic_cast 将p地址尝试转换成目标地址B + cout << p << " is class B" << endl; + } + if (dynamic_cast(p)){ + cout << p << " is class C" << endl; + } + return ; +} + + + + + + +int main() { + srand(time(0)); + A *p; + + switch (rand() % 2) { + case 0: + p = new B(); break; + case 1: + p = new C(); break; + } + + A *p1 = new B(), *p2 = new C(); + judge(p1); + judge(p2); + swap(((void **)p1)[0], ((void **)p2)[0]); + judge(p1); + judge(p2); + + + + + return 0; +} +``` + + + +``` +A constructor +B constructor +A constructor +B constructor +A constructor +C constructor +0x7ff780c05ad0 is class B +0x7ff780c05ae0 is class C +0x7ff780c05ad0 is class C +0x7ff780c05ae0 is class B +``` + + + +先判断p1p2是哪个类型,然后交换两个指针的虚函数表 + +结果dynamic_cast也发生了变换,说明dynamic_cast是根绝虚函数表判断指针类型的 + + + + + +![截屏2021-03-26 下午10.46.31](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-03-26%20%E4%B8%8B%E5%8D%8810.46.31.png) + + + + + + + +## 4.4纯虚函数 + + + +> 1.包含run是必须的 +> +> 2.实现run是不合理的 + +> 性质1:不实现纯虚函数的子类无法实例化 +> +> 实例化 = 创建对象 +> +> 用来规定子类中必须实现的方法 +> +> ==>接口:之规定对外的表现形式,没有具体功能,没有规定具体细节 +> +> 所以纯虚函数一般用来实现对外接口 + + + + + +```cpp +#include +using namespace std; + +class Animal { +public : + Animal(string name) : name(name) {} + virtual void run() = 0; // 纯虚函数 作用: 规定子类中必须实现的方法 + +private : + string name; +}; + +class House : public Animal { +public : + House() : Animal("House") {} + void run() override { + cout << "house, I can run wirh four legs!" << endl; + } +}; + + +int main() { + cout << "Hello world!" << endl; + House h; + + return 0; +} + +``` + + + +优先队列 + +```cpp +#include +#include +using namespace std; + +class IQueue { +public : + virtual void push(int) = 0; + virtual void pop() = 0; + virtual bool empty() = 0; + virtual int top() = 0; + virtual int size() = 0; +}; + +class vector_queue : public IQueue, public vector { +public : + void push(int x) override { + this->vector::push_back(x); + return ; + } + void pop() override { + if (empty()) return ; + vector::iterator p = this->begin(); + for (auto iter = begin(); iter != end(); iter++) { + if (*iter > *p) p = iter; + } + erase(p); + return ; + } + bool empty() override { + return size() == 0; + } + int top() override { + if (empty()) return 0; + int ans = at(0); + for (int i = 1; i < size(); i++) { + ans = max(at(i), ans); + } + return ans; + } + int size() override { + return this->vector::size(); + } +}; + +class heap_queue : public IQueue, public vector { +public : + void push(int x) override { + push_back(x); + up_maintain(size()); + return ; + } + void pop() override { + std::swap(at(0), at(size() - 1)); + pop_back(); + down_maintain(1); + return ; + } + bool empty() override { + return size() == 0; + } + int top() override { + if (empty()) return 0; + return at(0); + } + int size() override { + return this->vector::size(); + } + +private: + void up_maintain(int ind) { + while (ind > 1 && at(ind - 1) > at((ind / 2) - 1)) { + std::swap(at(ind - 1), at((ind / 2) - 1)); + ind /= 2; + } + return ; + } + void down_maintain(int ind) { + while (ind * 2 <= size()) { + int temp = ind; + if (at(ind * 2 - 1) > at(temp - 1)) temp = ind * 2; + if (ind * 2 + 1 <= size() && at(ind * 2) > at(temp - 1)) temp = ind * 2 + 1; + if (temp == ind) break; + std::swap(at(temp - 1), at(ind - 1)); + ind = temp; + } + return ; + } +}; + +int main() { + srand(time(0)); + vector_queue q1; + heap_queue q2; + for (int i = 0; i < 10; i++) { + int val = rand() % 100; + q1.push(val); + cout << "push q1 : " << val << endl; + } + while (!q1.empty()) { + cout << q1.top() << " "; + q1.pop(); + } + cout << endl; + for (int i = 0; i < 10; i++) { + int val = rand() % 100; + q2.push(val); + cout << "push q2 : " << val << endl; + } + while (!q2.empty()) { + cout << q2.top() << " "; + q2.pop(); + } + cout << endl; + return 0; +} + +``` + + + +## 4.5纯虚函数-接口 + +Animal就是抽象类,抽象类不生成对象 + +```cpp +class Animal { +public : + Animal(const string &name) : __name(name) {} + virtual void run() = 0; +protected : + string __name; +}; +``` + + + + + +## virtual总结 + + + + + +![截屏2021-03-26 下午11.55.14](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-03-26%20%E4%B8%8B%E5%8D%8811.55.14.png) + + + +![截屏2021-03-26 下午11.59.49](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-03-26%20%E4%B8%8B%E5%8D%8811.59.49.png) + + + +## 哈希表的实现 + +```cpp +#include +#include +#include +using namespace std; + +class Node { +public : + Node() = default; + Node (string, Node *); + string data(); + Node *next(); + void insert(Node *); + void erase_next(); +private : + string __data; + Node *__next; +}; + +class HashTable { +public : + typedef function HASH_FUNC_T; + HashTable(HASH_FUNC_T hash_func, int size); + bool insert(string); + bool erase(string); + bool find(string); +private : + int size; + HASH_FUNC_T hash_func; + vector data; +}; + +Node::Node(string data, Node *next = nullptr) : __data(data), __next(next) {} +string Node::data() { + return this->__data; +} +Node *Node::next() { + return this->__next; +} +void Node::insert(Node *p) { + p->__next = this->__next; + this->__next = p; +} +void Node::erase_next() { + if (this->__next == nullptr) return ; + Node *q = this->__next; + this->__next = this->__next->__next; + delete q; + return ; +} + +HashTable::HashTable(HASH_FUNC_T hash_func, int size = 10000) : size(size), data(size), hash_func(hash_func) {} +bool HashTable::insert(string s) { + if (find(s)) return false; + int h = hash_func(s) % size; + data[h].insert(new Node(s)); + return true; +} +bool HashTable::erase(string s) { + int h = hash_func(s) % size; + for (Node *p = &data[h]; p->next(); p = p->next()) { + if (p->next()->data() != s) continue; + p->erase_next(); + return true; + } + return false; +} +bool HashTable::find(string s) { + int h = hash_func(s) % size; + for (Node *p = data[h].next(); p; p = p->next()) { + if (p->data() == s) return true; + } + return false; +} + +int BKDRHash(string s) { + int seed = 31; + int h = 0; + for (int i = 0; s[i]; i++) { + h = h * seed + s[i]; + } + return h & 0x7fffffff; +} + +class APHash_Class { +public : + int operator()(string s) { + int h = 0; + for (int i = 0; s[i]; i++) { + if (i % 2) { + h = (h << 3) ^ s[i] ^ (h >> 5); + } else { + h = ~((h << 7) ^ s[i] ^ (h >> 11)); + } + } + return h & 0x7fffffff; + } +}; + +int main() { + APHash_Class APHash; + HashTable h1(BKDRHash); + HashTable h2(APHash); + + + int op; + string s; + while (cin >> op >> s) { + switch (op) { + case 0: + cout << "insert " << s << " to hash table 1 = "; + cout << h1.insert(s) << endl; + cout << "insert " << s << " to hash table 2 = "; + cout << h2.insert(s) << endl; + break; + case 1: + cout << "erase " << s << " from hash table 1 = "; + cout << h1.erase(s) << endl; + cout << "erase " << s << " form hash table 2 = "; + cout << h2.erase(s) << endl; + break; + case 2: + cout << "find " << s << " at hash table 1 = "; + cout << h1.find(s) << endl; + cout << "find " << s << " at hash table 2 = "; + cout << h2.find(s) << endl; + + break; + } + } + + + return 0; +} +``` + + + + + +优化后方案 + + + +```cpp +#include +#include +#include +using namespace std; + +class Node { +public : + Node() = default; + Node (string, int, Node *); + string key(); + Node *next(); + void set_next(Node *); + void insert(Node *); + void erase_next(); + int value; +private : + string __key; + Node *__next; +}; + +class HashTable { +public : + typedef function HASH_FUNC_T; + HashTable(HASH_FUNC_T hash_func, int size); + bool insert(string, int); + bool erase(string); + bool find(string); + int capacity(); + int &operator[](string); +private : + Node *__insert(string, int);// 返回新插入节点的地址 + Node *__find(string); + void __expand(); + int __size, data_cnt; + HASH_FUNC_T hash_func; + vector data; +}; + +Node::Node(string key, int value = 0, Node *next = nullptr) : __key(key), value(value), __next(next) {} +string Node::key() { + return this->__key; +} +Node *Node::next() { + return this->__next; +} +void Node::set_next(Node *next) { + this->__next = next; +} +void Node::insert(Node *p) { + p->__next = this->__next; + this->__next = p; +} +void Node::erase_next() { + if (this->__next == nullptr) return ; + Node *q = this->__next; + this->__next = this->__next->__next; + delete q; + return ; +} + +HashTable::HashTable(HASH_FUNC_T hash_func, int size = 2) : __size(size), data(__size), hash_func(hash_func), data_cnt(0) {} +bool HashTable::insert(string key, int value = 0) { + if (find(key)) return false; + return __insert(key, value); +} +int HashTable::capacity() { + return this->__size; +} +Node *HashTable::__insert(string key, int value = 0) {// 返回新插入节点的地址 + if (data_cnt >= __size) __expand(); + int h = hash_func(key) % __size; + data[h].insert(new Node(key, value)); + data_cnt += 1; + return data[h].next(); +} +void HashTable::__expand() { + cout << "expand hash table" << endl; + int new_size = __size * 2; + HashTable temp_h(hash_func, new_size); + for (int i = 0; i < __size; i++) { + for (Node *p = data[i].next(); p; p = p->next()) { + temp_h[p->key()] = p->value; + } + } + swap(*this, temp_h); + return ; +} +bool HashTable::erase(string s) { + int h = hash_func(s) % __size; + for (Node *p = &data[h]; p->next(); p = p->next()) { + if (p->next()->key() != s) continue; + p->erase_next(); + data_cnt -= 1; + return true; + } + return false; +} +bool HashTable::find(string s) { + return __find(s); +} + +Node *HashTable::__find(string key) { + int h = hash_func(key) % __size; + for (Node *p = data[h].next(); p; p = p->next()) { + if (p->key() == key) return p; + } + return nullptr; +} +int &HashTable::operator[](string key) { + Node *p; + if (!(p = __find(key))) return (__insert(key)->value); + return p->value; + +} + +int BKDRHash(string s) { + int seed = 31; + int h = 0; + for (int i = 0; s[i]; i++) { + h = h * seed + s[i]; + } + return h & 0x7fffffff; +} + +class APHash_Class { +public : + int operator()(string s) { + int h = 0; + for (int i = 0; s[i]; i++) { + if (i % 2) { + h = (h << 3) ^ s[i] ^ (h >> 5); + } else { + h = ~((h << 7) ^ s[i] ^ (h >> 11)); + } + } + return h & 0x7fffffff; + } +}; + +int main() { + APHash_Class APHash; + HashTable h1(BKDRHash); + HashTable h2(APHash); + + + int op; + string s; + cout << h1.capacity() << endl; + cout << h2.capacity() << endl; + + h1["hello"] = 123; + h1["word"] = 456; + h1["haizei"] = 789; + + cout << h1.capacity() << endl; + cout << h2.capacity() << endl; + + cout << h1["hello"] << " "<< h1["word"] << " " << h1["haha"] << endl; + while (cin >> op >> s) { + switch (op) { + case 0: + cout << "insert " << s << " to hash table 1 = "; + cout << h1.insert(s) << endl; + cout << "insert " << s << " to hash table 2 = "; + cout << h2.insert(s) << endl; + break; + case 1: + cout << "erase " << s << " from hash table 1 = "; + cout << h1.erase(s) << endl; + cout << "erase " << s << " form hash table 2 = "; + cout << h2.erase(s) << endl; + break; + case 2: + cout << "find " << s << " at hash table 1 = "; + cout << h1.find(s) << endl; + cout << "find " << s << " at hash table 2 = "; + cout << h2.find(s) << endl; + + break; + } + } + + + return 0; +} +``` + + + + + +## 4.6auto关键字及作用1 + + + +```cpp +#include +#include +#include +using namespace std; + +int main() { + + int x; + auto y = 12.3; // c++ auto : + // 根据变量自动定义类型, + // 在c语言就有auto: + // auto int z = 123; // C : 局部自动变量, 生存周期编译期自动决定,没有实际意义 + cout << sizeof(x) << endl; + cout << sizeof(y) << endl; + + map arr; + for (int i = 0; i < 10; i++) { + arr[rand() % 100] = rand(); + } + + // C++ auto 应用场景 + // vector 容器 封装好的数据结构 + // 需求:遍历容器中的相关元素 + // 遍历工具:迭代器,每一种容器的迭代器类型不一样 + map::iterator it = arr.begin();// map的迭代器 + while (it != arr.end()) { + cout << it->first << " " << it->second << endl; + it++; + } + cout << "++++++" << endl << endl; + auto iter = arr.begin();// map的迭代器 + while (iter != arr.end()) { + cout << iter->first << " " << iter->second << endl; + iter++; + } + + + return 0; +} +``` + + + +```cpp +4 +8 +7 282475249 +23 2007237709 +27 16531729 +30 470211272 +40 143542612 +44 1457850878 +73 984943658 +87 1137522503 +92 74243042 +++++++ + +7 282475249 +23 2007237709 +27 16531729 +30 470211272 +40 143542612 +44 1457850878 +73 984943658 +87 1137522503 +92 74243042 +``` + + + + + +> auto只根据赋值来决定类型 + + + +> 根据变量自动定义类型, +> 在c语言就有auto: +> auto int z = 123; // C : 局部自动变量, 生存周期编译期自动决定,没有实际意义 + + + +> C++ auto 应用场景 +> vector 容器 封装好的数据结构 +> 需求:遍历容器中的相关元素 +> 遍历工具:迭代器,每一种容器的迭代器类型不一样 +> map::iterator it = arr.begin();// map的迭代器 +> +> ​ 另一种写法 +> +> auto iter = arr.begin();// map的迭代器 + + + + + +c++11for循环的新的支持 + +```cpp +#include +#include +#include +using namespace std; + +int main() { + + + map arr; + for (int i = 0; i < 10; i++) { + arr[rand() % 100] = rand(); + } + + map::iterator it = arr.begin();// map的迭代器 + + for (pair i : arr) { + cout << i.first << " " << i.second << endl; + } + + cout << "++++++" << endl << endl; + for (auto i : arr) { + cout << i.first << " " << i.second << endl; + } + + + return 0; +} +``` + +```cpp +7 282475249 +23 2007237709 +27 16531729 +30 470211272 +40 143542612 +44 1457850878 +73 984943658 +87 1137522503 +92 74243042 +++++++ + +7 282475249 +23 2007237709 +27 16531729 +30 470211272 +40 143542612 +44 1457850878 +73 984943658 +87 1137522503 +92 74243042 +``` + + + +## typeid关键字 + + + +```cpp +#include +#include +#include +using namespace std; + +class Base { +public : +}; + +int main() { + + int x; + auto y = 12.3; // c++ auto : + int z = 123; + // 根据变量自动定义类型, + // 在c语言就有auto: + // auto int z = 123; // C : 局部自动变量, 生存周期编译期自动决定,没有实际意义 + + cout << typeid(x).name() << endl; // 返回类型信息对象,.name输出类型信息名字 + cout << typeid(y).name() << endl; // 返回类型信息对象,.name输出类型信息名字 + cout << typeid(z).name() << endl; // 返回类型信息对象,.name输出类型信息名字 + + Base b; + cout << typeid(b).name() << endl; + + if (typeid(y).hash_code() == typeid(float).hash_code()) { + cout << "float type" << endl; + } + if (typeid(y).hash_code() == typeid(double).hash_code()) { + cout << "double type" << endl; + } + return 0; +} + +``` + + + + + +## auto关键字4个限制 + +> 1.不能作为函数参数(c++11) + +```cpp +void func(auto x, auto y) {} +void func(auto x, double y) {} +func(1.2, 1.2); +func(1, 1); +``` + +编译期功能 + +> 2.c++11不能作为模板参数 + +> 3.不能定义数组 + +> 4.不能用于非静态成员变量 + +```cpp +class Base { +public : + static int x; // 静态成员属性 + auto y;// 对象属性不能使用auto关键字来定义 + //non-static data member declared with placeholder 'auto' +}; + +auto Base::x = 123; + +``` + + + + + +## constexpr关键字 + +> const 运行期常量 +> constexpr 编译期常量 叫作表达式常量 + +constexpr所定义的常量就是告诉编译器在编译期就可以确定的值 + + + +```cpp +#include +using namespace std; + +int main() { + + // const 运行期常量 + // constexpr 编译期常量 叫作表达式常量 + + + const int x = 123; + constexpr int y = 123; + + // 都会报错 + // x = 456; + // y = 456; + + + *(const_cast(&x)) = 456; // 转换常量 + *(const_cast(&y)) = 456; // 转换常量 + cout << x << endl; + cout << y << endl; + cout << const_cast (&x) << endl; + cout << const_cast (&y) << endl; + cout << &x << endl; + cout << &y << endl; + cout << *(&x) << endl; // & 取地址是运行期的,躲避了编译期优化 + cout << *(&y) << endl; // & 取地址是运行期的,躲避了编译期优化 + + return 0; +} +``` + + + + + +```cpp +#include +using namespace std; + +int main() { + + int n; + cin >> n; + const int x = n + 123; // 运行期常量 + constexpr int y = n + 123; // 报错 + //the value of 'n' is not usable in a constant expression +} +``` + + + +constexpr应用场景:在编译期就可以确定的函数和变量 + +```cpp +#include +using namespace std; + +constexpr int f(int x) { + return x * x; +} + +int main() { + + int n; + cin >> n; + const int a = n + 123; // 运行期常量 + constexpr int y1 = 123; // + constexpr int y2 = f(123); +} +``` + + + +constexpr应用场景:修饰类,修饰对象 + +```cpp +#include +using namespace std; +class A { +public : + constexpr A(int x) {} +}; + + +int main() { + + int n; + cin >> n; + const int a = n + 123; // 运行期常量 + constexpr int y1 = 123; // + constexpr int y2 = f(123); + const A a1(123); + constexpr A a2(123); +} +``` + + + +## nullptr关键字 + +```cpp +#include +#include +#include +#include +using namespace std; + +void func(int x) { + cout << __PRETTY_FUNCTION__ << endl; // 输出函数具体形式 + cout << x << endl; + return ; +} + +void func(int *x) { + cout << __PRETTY_FUNCTION__ << endl; + cout << x << endl; + return ; +} + + +int main() { + + + //cout << NULL << endl; // 0 + //cout << (nullptr) << endl; // nullptr + + /*if (NULL) { // false + cout << "true" << endl; + } else { + cout << "false" << endl; + } + if (nullptr) { // true + cout << "true" << endl; + } else { + cout << "false" << endl; + } + +*/ + func(nullptr); // void func(int*) 0 + + //func(NULL); // call of overloaded 'func(NULL)' is ambiguous + // NULL代表整数0,和空地址, 编译器不确定调用哪一个函数 + // nullptr : 更加准确的空地址信息 + + + return 0; +} +``` + + + + + +>NULL代表整数0,和空地址, +>nullptr : 更加准确的空地址信息 + + + +## 左值和右值 + +左值 + +右值 + +左值引用 + +右值引用 + + + +```cpp +#include +using namespace std; + +#define func(x) __func(x, "func("#x")") + +void __func(int &x, const char *str) { + // 判断是左值引用还是右值引用 + cout << str << " is left value" << endl; + return ; +} + +void __func(int &&x, const char *str) { + cout << str << " is right value" << endl; + return ; +} + +int main() { + + int x = 1234, y = 456; + int &rx = x; // 左值引用 + + func(1234); + func(x); // 可以通过x访问 + func(x + y); + func(x++); // x+1存储在另一个地址,x+1之前的值无法被访问 + func(++x); // x+1之后的值存储在x,x+1之后的值可以通过x访问 + return 0; +} +``` + + + +> 如何判断是左值还是右值? +> +> 到了代码下一行的时候,是否能通过单一变量访问到相关的值 +> +> 能==>左值 +> +> 不能==>右值 +> +> 字面量一定是右值 + + + +```cpp +class A { +public : + A operator+(int x) {} // 应该返回右值,返回值 + A &operator+=(int x) {} // 应该返回左值类型,所以返回了应该引用 +}; + +``` + + + + + +## **move**和forward + +```cpp +#include +using namespace std; + +#define func(x) __func(x, "func("#x")") +#define func2(x) __func2(x, "func2("#x")") + +void __func2(int &x, const char *str) { + // 判断是左值引用还是右值引用 + cout << str << " is left value" << endl; + return ; +} +void __func2(int &&x, const char *str) { + // 判断是左值引用还是右值引用 + cout << str << " is right value" << endl; + return ; +} + +void __func(int &x, const char *str) { + // 判断是左值引用还是右值引用 + cout << str << " is left value" << endl; + func2(move(x)); + return ; +} + +void __func(int &&x, const char *str) { + cout << str << " is right value" << endl; + func2(move(x)); + return ; +} + +class A { +public : + A operator+(int x) {} // 应该返回右值,返回值 + A &operator+=(int x) {} // 应该返回左值类型,所以返回了应该引用 +}; + + + +int main() { + + int x = 1234, y = 456; + int &rx = x; // 左值引用 + + func(1234); + func(x); + func(x + y); + func(x++); + func(++x); + func(x + 123); + func(x += 123); + func(x *= 2); + func(y += 3); + func(y * 3); + + + return 0; +} +``` + + + +```cpp +func(1234) is right value +func2(move(x)) is right value +func(x) is left value +func2(move(x)) is right value +func(x + y) is right value +func2(move(x)) is right value +func(x++) is right value +func2(move(x)) is right value +func(++x) is left value +func2(move(x)) is right value +func(x + 123) is right value +func2(move(x)) is right value +func(x += 123) is left value +func2(move(x)) is right value +func(x *= 2) is left value +func2(move(x)) is right value +func(y += 3) is left value +func2(move(x)) is right value +func(y * 3) is right value +func2(move(x)) is right value +``` + + + +传递结果是右值 + + + +```cpp +#include +using namespace std; + +#define func(x) __func(x, "func("#x")") +#define func2(x) __func2(x, "func2("#x")") + +void __func2(int &x, const char *str) { + // 判断是左值引用还是右值引用 + cout << str << " is left value" << endl; + return ; +} +void __func2(int &&x, const char *str) { + // 判断是左值引用还是右值引用 + cout << str << " is right value" << endl; + return ; +} + +void __func(int &x, const char *str) { + // 判断是左值引用还是右值引用 + cout << str << " is left value" << endl; + func2(x); + return ; +} + +void __func(int &&x, const char *str) { + cout << str << " is right value" << endl; + func2(forward(x)); + return ; +} + +class A { +public : + A operator+(int x) {} // 应该返回右值,返回值 + A &operator+=(int x) {} // 应该返回左值类型,所以返回了应该引用 +}; + + + +int main() { + + int x = 1234, y = 456; + int &rx = x; // 左值引用 + + func(1234); + func(x); + func(x + y); + func(x++); + func(++x); + func(x + 123); + func(x += 123); + func(x *= 2); + func(y += 3); + func(y * 3); + + + + + return 0; +} + +``` + + + + + +```cpp +func(1234) is right value +func2(forward(x)) is right value +func(x) is left value +func2(x) is left value +func(x + y) is right value +func2(forward(x)) is right value +func(x++) is right value +func2(forward(x)) is right value +func(++x) is left value +func2(x) is left value +func(x + 123) is right value +func2(forward(x)) is right value +func(x += 123) is left value +func2(x) is left value +func(x *= 2) is left value +func2(x) is left value +func(y += 3) is left value +func2(x) is left value +func(y * 3) is right value +func2(forward(x)) is right value +``` + + + + + + + +move强制转换成右值 + +forward将当前值转换成目标类型 + + + +## 右值引用场景vector + +```cpp +#include +using namespace std; + +namespace guziqiu { + +class vector { +public : + vector(int n = 10) : __size(n), data(new int[n]) { + cout << "default constructor" << endl; + } + vector(const vector &v) : __size(v.size()), data(new int[__size]) { // 拷贝构造 + cout << "deep copy constructor" << endl; + for (int i = 0; i < size(); i++) { + data[i] = v[i]; + } + return ; + } + vector operator+(const vector &v){ // 合并两个vector + vector ret(v.size() + this->size()); + vector &now = *this; + for (int i = 0; i < size(); i++) { + ret[i] = now[i]; + } + for (int i = size(); i < ret.size(); i++) { + ret[i] = v[i - size()]; + } + return ret; + } + int &operator[](int ind) const { + return this->data[ind]; + } + int size() const { return __size; } +private: + int __size; + int *data; +}; + + +} // end of guziqiu + +ostream &operator<<(ostream &out, const guziqiu::vector &v) { + for (int i = 0; i < v.size(); i++) { + out << v[i] << " "; + } + out << endl; + return out; +} + + + +int main() { + + guziqiu::vector v1, v2; + for (int i = 0; i < v1.size(); i++) v1[i] = rand() % 100; + for (int i = 0; i < v2.size(); i++) v2[i] = rand() % 100; + + guziqiu::vector v3(v1 + v2); + cout << v1 << endl; + cout << v2 << endl; + cout << v3 << endl; + + + return 0; +} +``` + + + +```cpp +g++ -fno-elide-constructors vector.cpp +default constructor +default constructor +default constructor +deep copy constructor +deep copy constructor +7 49 73 58 30 72 44 78 23 9 + +40 65 92 42 87 3 27 29 40 12 + +7 49 73 58 30 72 44 78 23 9 40 65 92 42 87 3 27 29 40 12 +``` + +关掉编译器优化后显示发生了两次拷贝行为 + + + + + + + + + +## 右值引用移动构造 + + + +```cpp +#include +using namespace std; + +namespace guziqiu { + +class vector { +public : + vector(int n = 10) : __size(n), data(new int[n]) { + cout << "default constructor" << endl; + } + vector(const vector &v) : __size(v.size()), data(new int[__size]) { // 拷贝构造 + cout << "deep copy constructor" << endl; + for (int i = 0; i < size(); i++) { + data[i] = v[i]; + } + return ; + } + vector(vector &&v) : __size(v.size()), data(v.data) { // 移动构造, 传入右值引用构造函数, + cout << "move copy constructor" << endl; + v.data = nullptr; + v.__size = 0; + } + vector operator+(const vector &v){ // 合并两个vector + vector ret(v.size() + this->size()); + vector &now = *this; + for (int i = 0; i < size(); i++) { + ret[i] = now[i]; + } + for (int i = size(); i < ret.size(); i++) { + ret[i] = v[i - size()]; + } + return ret; + } + int &operator[](int ind) const { + return this->data[ind]; + } + int size() const { return __size; } + ~vector() { + if (data) delete[] data; + data = nullptr; + __size = 0; + } +private: + int __size; + int *data; +}; + + +} // end of guziqiu + +ostream &operator<<(ostream &out, const guziqiu::vector &v) { + for (int i = 0; i < v.size(); i++) { + out << v[i] << " "; + } + out << endl; + return out; +} + + + +int main() { + + guziqiu::vector v1, v2; + for (int i = 0; i < v1.size(); i++) v1[i] = rand() % 100; + for (int i = 0; i < v2.size(); i++) v2[i] = rand() % 100; + + guziqiu::vector v3(v1 + v2); + cout << v1 << endl; + cout << v2 << endl; + cout << v3 << endl; + + + return 0; +} +``` + + + +```cpp +g++ -fno-elide-constructors 42.vector.cpp +default constructor +default constructor +default constructor +move copy constructor +move copy constructor +7 49 73 58 30 72 44 78 23 9 + +40 65 92 42 87 3 27 29 40 12 + +7 49 73 58 30 72 44 78 23 9 40 65 92 42 87 3 27 29 40 12 + +``` + + + +发生了两次移动构造 + + 移动构造, 传如右值引用构造函数, + +> 移动构造的好处: +> +> 减少匿名变量空间的开辟 +> +> 速度变快,直接指向了变量地址 + + + +```cpp +#include +using namespace std; + +void func2(int &x) { + cout << __PRETTY_FUNCTION__ << "called" << endl; +} + +void func2(const int &x) { // 可以绑定任意版本 + cout << __PRETTY_FUNCTION__ << "called" << endl; +} + +void func2(int &&x) { + cout << __PRETTY_FUNCTION__ << "called" << endl; +} + +void func2(const int &&x) { + cout << __PRETTY_FUNCTION__ << "called" << endl; +} + + +int main() { + int n; + const int y = 123; + // 优先绑定到最匹配的上面 + func2(n); // func2(int &) // 优先绑定到左值引用 + func2(y); // func2(const int &) + func2(123 + 456); // func1(int &&) + // func1(const int &&) + + return 0; +} +``` + + + +```cpp +void func2(int&)called +void func2(const int&)called +void func2(int&&)calledc +``` + + + + + +# 5.模板 + +泛型编程 + +## 5.1模板函数和模板类 + +> 泛型编程: +> +> 将==任意类型==从程序设计中抽象出来 + + + +| | 泛型编程 | +| ------------ | -------------------- | +| 面向过程编程 | 用模板实现函数的过程 | +| 面向对象编程 | 用模板实现类 | + + + +模板函数 + +```cpp +template +T add(T a, T b) { + return a + b; +} +``` + +> 模板的声明 +> +> 在编译过程中,将模板生成相关代码,称为模板的实例化, +> +> 编译期看到的是实例化后的代码 +> +> 模板的代码在运行的时候已经被干掉了 +> +> 所以模板的代码必须写到头文件中 + +模板类 + +```cpp +template +struct PrintAny{ + PrintAny(std::ostream &out) : out(out) {} + void operator() (const T &a) { + out << a; + } + std::ostream &out; +}; +``` + + + +模板实例化 + +模板在运行期才会实例化 + +## 模板函数 + +```cpp +#include +using namespace std; + +template +decltype(T() + U()) add(T a, U b) { // decltype 类型锈化 调用了T的默认构造函数 + return a + b; +} + +/* +int add(int a, int b) { + return a + b; +} +int add(double a, double b) { + return a + b; +} +*/ +int main() { + + cout << add(1, 2) << endl; + cout << add(1.2 ,2.2) << endl; + cout << add(1 ,2.2) << endl; // 强制将int转换成double + cout << add(1 ,2.2) << endl; // 改写模板 + + decltype(1 + 2) x; // 根据(1 + 2)类型推导出 x 的类型 + if (typeid(x).hash_code() == typeid(int).hash_code()) cout << "int" << endl; + + return 0; +} +``` + + + +``` +3 +3.4 +3.2 +3.2 +int +``` + + + + + +> decltype 返回值必须有默认构造函数才行,没有默认构造怎么办? + + ==返回值后置== + + + +任意类型相加无bug版 + +```cpp +#include +using namespace std; + +class A { +public : + A() = delete; + A(int x) : x(x) {} + A operator+(const A &a) { + return A(x + a.x); + } + friend ostream &operator<<(ostream &, const A &); +private : + int x; +}; + +ostream &operator<<(ostream &out, const A &a) { + out << a.x << endl; + return out; +} + + +template +auto add(T a, U b) -> decltype(a + b){ // decltype 类型锈化 调用了T的默认构造函数 + return a + b; +} + +auto func(int a, int b) -> int { // 返回值后置 + return a + b; +} + +int main() { + A a(3), b(5); + cout << (a + b) << endl; + cout << add(a, b) << endl; + + cout << add(1, 2) << endl; + cout << add(1.2 ,2.2) << endl; + cout << add(1 ,2.2) << endl; // 强制将int转换成double + cout << add(1 ,2.2) << endl; // 改写模板 + + decltype(1 + 2) x; // 根据(1 + 2)类型推导出 x 的类型 + if (typeid(x).hash_code() == typeid(int).hash_code()) cout << "int" << endl; + + return 0; +} + +``` + +```cpp +8 + +8 + +3 +3.4 +3.2 +3.2 +int +``` + + + +```cpp +auto func(int a, int b) -> int { // 返回值后置 + return a + b; +} +``` + + + +## 模板类 + +```cpp +#include +using namespace std; + +template // 模板类 +class PRINT { // 可以打印任意类型的元素 +public : + PRINT &operator()(T a) { + cout << a << endl; + return *this; + } +}; + +class PRINT2 { // 可以打印任意类型的元素 +public : + template // 模板成员方法 + PRINT2 &operator()(T a) { + cout << a << endl; + return *this; + } +}; + +int main() { + PRINT print_int; + print_int(123); + print_int(3455); + PRINT print_str; + print_str("hello guziqiu"); + print_str("hello haizei"); + + // 打印任意类型 + PRINT2 print; + print(123)("hello guziqiu")(4456); + + + + return 0; +} +``` + + + +```cpp +123 +3455 +hello guziqiu +hello haizei +123 +hello guziqiu +4456 +``` + + + +## 模板类实现vector + +```cpp +#include +#include +using namespace std; + +class A { +public: + A() = delete; + A(int x) : x(x) {} + friend ostream &operator<<(ostream &, const A &); +private : + int x; +}; + +ostream &operator<<(ostream &out, const A &a) { + out << "Class A :" << a.x << " "; + return out; +} + +namespace guziqiu { +template +class vector { +public : + vector(int n = 10); + vector(const vector &); + vector(vector &); + T &operator[](int); + const T &operator[](int) const; + int size() const; + void push_back(const T &); + void push_back(T &&); + ~vector(); +private : + int __capacity; + int __size; + T *data; +}; + +template +vector::vector(int n) : __capacity(n), __size(0), data(nullptr) { // bug 1 data(new T[__size]) 如果T类没有默认构造函数会报错, + data = (T *)malloc(sizeof(T) * __capacity); + return ; +} + +template +vector::vector(const vector &v) : __size(v.size), __capacity(v.__capacity) { // 深拷贝 + //__size = v.__size; + data = (T *)malloc(sizeof(T) * __capacity); + for (int i = 0; i < __size; i++) { + new(data + i) T(v.data[i]); // 原地构造 + //data[i] = v.data[i]; data[i]的存储区可能没有被构造 + } + return ; +} + +template +vector::vector(vector &v) : __size(v.__size), data(v.data), __capacity(v.__capacity) { // 移动构造 + v.data = nullptr; + v.__size = 0; + v.__capacity = 0; +} + +template +T &vector::operator[](int ind) { + return data[ind]; +} + +template +const T &vector::operator[](int ind) const { + return data[ind]; +} + +template +int vector::size() const { return __size; } + +template +void vector::push_back(const T &d) { + new(data + __size) T(d); // 调用相关类型的拷贝构造 + __size += 1; + return ; +} +template +void vector::push_back(T &&d) { + new(data + __size) T(move(d)); // 调用相关类型的移动构造 + __size += 1; + return ; +} + +template +vector::~vector() { + if (data) free(data); + __size == 0; + __capacity = 0; + return ; +} + +} // end of guziqiu + +ostream &operator<<(ostream &out, const vector &v) { + for (int i = 0; i < v.size(); i++) { + cout << v[i] << " "; + } + return out; +} + +ostream &operator<<(ostream &out, const guziqiu::vector &v) { + for (int i = 0; i < v.size(); i++) { + cout << v[i] << " "; + } + return out; +} + + +int main() { + A a(1); + cout << "vector :" << endl; + //const vector v; + vector v; + //v.push_back(a); + cout << v.size() << endl; + v.push_back(123); + v.push_back(456); + v.push_back(789); + cout << v << endl; + cout << endl << "my vector :" << endl; + //const guziqiu::vector v2; + guziqiu::vector v2; + cout << v2.size() << endl; + v2.push_back(123); + v2.push_back(456); + v2.push_back(789); + cout << v2 << endl; + + + return 0; +} +``` + + + +```cpp +vector : +0 +Class A :123 Class A :456 Class A :789 + +my vector : +0 +Class A :123 Class A :456 Class A :789 +``` + + + + + +## 引用折叠 + + + +```cpp +#include +using namespace std; +namespace guziqiu { +template +void swap(T &&a, T &&b) { + T c; // declared as reference but not initialized + c = a; + a = b; + b = c; + return ; +} +} // end of guziqiu +int main() { + int n = 123, m = 456; + guziqiu::swap(n, m); + cout << n << " " << m << endl; + + //guziqiu::swap(789, m);报错 + //cout << n << " " << m << endl; + + return 0; +} +``` + + + +模板在实例化时调用swap + +因为n绑定到了引用上,编译器将 T &&转换为左值引用(因为n是左值引用), + +T 将会被推导为int &,所以T &&a== int &&&a, int &&&发生引用折叠—取奇数个&,最后还是int &a + +```cpp +#include +using namespace std; + + +namespace guziqiu { +template +void swap(T &&a, T &&b) { + typename remove_reference::type c; // declared as reference but not initialized + // remove_reference 去掉相关类型的引用, T == T &c = a; + c = a; + a = b; + b = c; + return ; +} +} // end of guziqiu + + +int main() { + int n = 123, m = 456; + guziqiu::swap(n, m); + cout << n << " " << m << endl; + + //guziqiu::swap(789, m); + //cout << n << " " << m << endl; + + + + return 0; +} +``` + + + + + +> typename remove_reference::type去掉相关类型的引用, + + + +## 模板特化和模板偏特化 + +```cpp +#include +using namespace std; + +class A { +public : + A() = delete; + A(int x) : x(x) {} + A operator+(const A &a) { + return A(x + a.x); + } + friend ostream &operator<<(ostream &, const A &); +private : + int x; +}; + +ostream &operator<<(ostream &out, const A &a) { + out << a.x << endl; + return out; +} + +template +auto add(T a, U b) -> decltype(a + b){ + return a + b; +} + + +template // 模板偏特化,传入的是指针类型的变量 +auto add(T *a, U *b) -> decltype(*a + *b){ // decltype 类型锈化 调用了T的默认构造函数 + return *a + *b; +} + +template<> // 模板的特化,特殊处理 +int add(int a, int b) { + return a + b * 2; +} + + +auto func(int a, int b) -> int { + return a + b; +} + +int main() { + cout << "+++++++++++++" << endl; + int n = 123, m = 456; + int *p = &n, *q = &m; + int **pp = &p, **qq = &q; + + cout << add(p, q) << endl; // 模板偏特化 传指针, + cout << "++++" << endl; + //cout << add(pp, qq) << endl; // 模板偏特化 传指针, + + cout << add(1, 2) << endl; // 调用的是特化版本 + + + return 0; +} +``` + +```cpp ++++++++++++++ +579 +++++ +5 +``` + + + + + +## 模板类特化 + +![截屏2021-03-28 下午8.06.13](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-03-28%20%E4%B8%8B%E5%8D%888.06.13.png) + + + +## 变参模板 + + + +```cpp +#include +using namespace std; + +template +void print(T a) { // 让递归终止在偏特化版本 + cout << a << endl; + return ; +} + + +template +void print(T a, ARGS... args) { // 打印任意参数 + cout << a << endl; + print(args...); // 每一次递归减少一个参数 + return ; +} + + +int main() { + print(123, 34.6); + print(123, "hello world", "haizei", 45.6); + return 0; +} +``` + + + +```cpp +123 +34.6 +123 +hello world +haizei +45.6 +``` + + + + + + + +## 模板偏特化与可变参数模板 + +![截屏2021-03-28 下午8.06.20](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-03-28%20%E4%B8%8B%E5%8D%888.06.20.png) + +![截屏2021-03-28 下午8.06.47](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-03-28%20%E4%B8%8B%E5%8D%888.06.47.png) + +## 模板偏特化实现解析变参列表小工具 + +```cpp +#include +#include +using namespace std; + +template +struct N_ARGS { // 解析变参列表小工具 + typedef T type; + typedef N_ARGS rest; +}; + +template // 偏特化版 +struct N_ARGS { + typedef T type; + typedef T last; // 控制变参列表的最后一个参数,不是最后一个会报错 +}; + +template +void print(T a) { // 让递归终止在偏特化版本 + cout << a << endl; + return ; +} + +template +void print(T a, ARGS... args) { // 打印任意参数 + cout << a << endl; + cout << a << " next type : " << typeid(typename N_ARGS::type).name() << endl; + print(args...); // 每一次递归减少一个参数 +// N_ARGS::type;// 第一个变量名称 +// N_ARGS::rest; // 剩余变量名称 + return ; +} + +int main() { + print(123, 34.6); + print(123, "hello world", "haizei", 45.6); + + N_ARGS::type x; + N_ARGS::rest::type y; + N_ARGS::rest::rest::type z; + N_ARGS::rest::rest::rest::last w; + + cout << typeid(x).name() << endl; + cout << typeid(y).name() << endl; + cout << typeid(z).name() << endl; + cout << typeid(w).name() << endl; + + return 0; +} +``` + + + +```cpp +123 +123 next type : d +34.6 +123 +123 next type : PKc +hello world +hello world next type : PKc +haizei +haizei next type : d +45.6 +i +i +d +d +``` + + + +//优化 -- 获取指定的第n个参数 + +```cpp +#include +#include +using namespace std; + +template +struct N_ARGS { // 解析变参列表小工具 + typedef T type; + typedef N_ARGS rest; +}; + +template // 偏特化版 +struct N_ARGS { + typedef T type; + typedef T last; // 控制变参列表的最后一个参数,不是最后一个会报错 +}; + + +// 获取指定的第n个参数 +template +struct NewN_ARGS { // 解析变参列表小工具 + typedef typename NewN_ARGS::type type; + static int last() { // 是最后一个参数返回1 + return NewN_ARGS::last(); + } +}; + +template // 偏特化版 +struct NewN_ARGS<1, T, ARGS...> { + typedef T type; + static int last() { return 0; } +}; + +template // 偏特化版 +struct NewN_ARGS<1, T> { + typedef T type; + static int last() { return 1; } +}; + + +template +void print(T a) { // 让递归终止在偏特化版本 + cout << a << endl; + return ; +} + +template +void print(T a, ARGS... args) { // 打印任意参数 + cout << a << endl; + cout << a << " next type : " << typeid(typename N_ARGS::type).name() << endl; + print(args...); // 每一次递归减少一个参数 +// N_ARGS::type;// 第一个变量名称 +// N_ARGS::rest; // 剩余变量名称 + return ; +} + +#define Test(func...) cout << #func << " = " << func << endl + +int main() { + print(123, 34.6); + print(123, "hello world", "haizei", 45.6); + + + N_ARGS::type x; + N_ARGS::rest::type y; + N_ARGS::rest::rest::type z; + N_ARGS::rest::rest::rest::last w; + + cout << typeid(x).name() << endl; + cout << typeid(y).name() << endl; + cout << typeid(z).name() << endl; + cout << typeid(w).name() << endl; + + + NewN_ARGS<1, int, int, double, double>::type x1; + NewN_ARGS<2, int, int, double, double>::type x2; + NewN_ARGS<3, int, int, double, double>::type x3; + NewN_ARGS<4, int, int, double, double>::type x4; + + + Test(NewN_ARGS<1, int, int, double, double>::last()); + Test(NewN_ARGS<2, int, int, double, double>::last()); + Test(NewN_ARGS<3, int, int, double, double>::last()); + Test(NewN_ARGS<4, int, int, double, double>::last()); + + + + return 0; +} +``` + +```cpp +123 +123 next type : d +34.6 +123 +123 next type : PKc +hello world +hello world next type : PKc +haizei +haizei next type : d +45.6 +i +i +d +d +NewN_ARGS<1, int, int, double, double>::last() = 0 +NewN_ARGS<2, int, int, double, double>::last() = 0 +NewN_ARGS<3, int, int, double, double>::last() = 0 +NewN_ARGS<4, int, int, double, double>::last() = 1 +``` + + + + + + + + + +## 模板的图灵完备性--判断素数 + +模板的图灵完备性:模板可以在编译期完成一套完整的流程 + + + +```cpp +#include +using namespace std; + + +template +struct getNextI { + static constexpr int r = (n % i ? i + 1 : 0); +}; + +template +struct getNextN { + static constexpr int r = (i * i <= n ? n : 0); +}; + +template +struct __test_prime { + static constexpr int r = __test_prime::r, getNextN::r>::r; +}; +template +struct __test_prime<0, n> { + static constexpr int r = 0; +}; + +template +struct __test_prime { + static constexpr int r = 1; +}; +template<> +struct __test_prime<0, 0> { + static constexpr int r = 1; +}; + +template +struct if_condition { + static constexpr const char *r = (n ? "YES" : "NO"); +}; + +template +struct is_prime { + static constexpr const char * r = if_condition<__test_prime<2, n>::r>::r; +}; + + + +int main() { + + cout << is_prime<2>::r << endl; // 1 + cout << is_prime<13>::r << endl; // 1 + cout << is_prime<25>::r << endl; // 0 + cout << is_prime<27>::r << endl; // 0 + cout << is_prime<9973>::r << endl; // 1 + cout << is_prime<1234>::r << endl; // 0 + + + return 0; +} +``` + + + + + +## 模板类实现循环判断 + +```cpp +#include +using namespace std; + +template +struct is_prime { + static constexpr int r = 0; +}; + + + +// 模板类实现递归 循环求和 +template +struct sum { + static constexpr int r = n + sum::r; +}; + +template<> +struct sum<1> { // 特化版本 递归终结处 + static constexpr int r = 1; +}; + +// 模板类实现 if判断 +template +struct getBad { + static constexpr int r = (n <= 5); +}; +template +struct getGood { + static constexpr int r = (n > 5); +}; + +templatestruct judge; +template<> +struct judge<1, 0> { + static constexpr char *const r = (char *)"bad"; +}; +template<> +struct judge<0, 1> { + static constexpr char *const r = (char *)"good"; +}; + +template +struct score { + static constexpr char *const r = judge::r, getGood::r>::r; +}; + + +//is_even 判断奇数偶数 +template +struct isEven { + static constexpr int r = !(n % 2); +}; +templatestruct judge1; +template<> +struct judge1<0> { + static constexpr char *const r = (char *)"no"; +}; +template<> +struct judge1<1> { + static constexpr char *const r = (char *)"yes"; +}; +template +struct is_even { + static constexpr char *r = judge1::r>::r; +}; + + + +int main() { + cout << sum<5>::r << endl; // 1~5的和 模板类实现求和 + cout << score<10>::r << endl; // good 模板实现分支判断 + cout << score<4>::r << endl; // bad + cout << is_even<3>::r << endl; // no 判断奇数偶数 + cout << is_even<4>::r << endl; // yes + + cout << is_prime<2>::r << endl; // 1 + cout << is_prime<13>::r << endl; // 1 + cout << is_prime<25>::r << endl; // 0 + cout << is_prime<27>::r << endl; // 0 + cout << is_prime<9973>::r << endl; // 1 + cout << is_prime<1234>::r << endl; // 0 + + + + + return 0; +} +``` + +```cpp +15 +good +bad +no +yes +0 +0 +0 +0 +0 +0 +``` + + + +## 模板与宏定义的区别 + + + +模板能在编译期完成一套完整的流程 + +宏定义只是简单的替换 + +程序一般是在运行期完成的 + + + + + +## 5.2异常throw + + + +```cpp +#include +using namespace std; + +int main() { + int age = 100; + + // cin >> age; + try { + if (age > 90) { + cout << "before throw" << endl; + throw(age); + cout << "123" << endl; + } else { + cout << age << "---" << endl; + } + } + + catch(int age) { + cout << age << " is too big" << endl; + } + + + return 0; +} +``` + + + +```cpp +before throw +100 is too big +``` + + + +> 抛出异常 +> +> throw后面的语句不会被执行 + +## throw代码演示1 + +```cpp +#include +using namespace std; + +class guziqiu : public exception { +public : + guziqiu() = default; + guziqiu& operator=(const guziqiu &) = default; + + const char* what() { // 解释异常 + return "I'm don't know what happened !"; + } + + + ~guziqiu() = default; +}; + + +void inner() { + try { + throw(guziqiu()); + } + catch(guziqiu &e) {// 捕获异常按顺序执行 + cout << "inner exception caught first" << endl; + } + + + catch(guziqiu &e) { + cout << e.what() << "inner exception caught second " << endl; + } +} + +void outer() { + try { + inner(); + } + catch(...) { // 捕获任意异常 + cout << "outer expection caught" << endl; + } +} + + +int main() { + int age = 100; + + outer(); + + + + // cin >> age; + try { + if (age > 90) { + cout << "before throw" << endl; + throw(age); + cout << "123" << endl; + } else { + cout << age << "---" << endl; + } + } + + catch(int age) { + cout << age << " is too big" << endl; + } + + + return 0; +} +``` + + + +```cpp +inner exception caught first +before throw +100 is too big +``` + + + +>1.catch捕获异常按顺序执行 +> +>2.`catch(...)`代表捕获所有异常 +> +>3.如果函数内部有捕获异常的处理,则会优先被内层捕获 + + + +## throw代码演示2 + + + +```cpp +#include +using namespace std; + +class Hellper { +public : + Hellper() { + cout << "constructor called" << endl; + } + ~Hellper() { + cout << "deconstructor called" << endl; + } +}; + +void inner() { + cout << "inner begin " << endl; + try { + Hellper h = Hellper(); + throw("123"); + } + + catch(...) { + cout << "exception caught" << endl; + } + cout << "inner endl" << endl; + +} + +void outer() { + try { + cout << "outer begin" << endl; + inner(); + } + catch(...) { + cout << "outer exception caught" << endl; + } + cout << "outer endl" << endl; +} + + +int main() { + + outer(); + + return 0; +} +``` + + + +```cpp +outer begin +inner begin +constructor called +deconstructor called +exception caught +inner endl +outer endl +``` + + + + + +> 在抛出异常之前Helper已经被析构 + + + +## throw-& + +```cpp +#include +using namespace std; + +class Hellper { +public : + Hellper() { + cout << "constructor called" << endl; + } + ~Hellper() { + cout << "deconstructor called" << endl; + } +}; + +class myerror : public exception { +public : + const char * what() { + return "expection myerror"; + } + string s; + +}; + + +void inner() { + cout << "inner begin " << endl; + try { + Hellper h = Hellper(); + throw("123"); + } + + catch(...) { + cout << "exception caught" << endl; + } + cout << "inner endl" << endl; + +} + +void outer() { + try { + cout << "outer begin" << endl; + throw myerror(); + //inner(); + } + catch(myerror err) { // catch(myerror& err) { + err.s = "hello"; + cout << "outer exception caught err" << endl; + cout << "&err.s" << &err.s << " : " << err.s << endl; + throw;// 继续向上层抛出异常 + } + cout << "outer endl" << endl; +} + + +int main() { + try { + outer(); + } + catch(myerror err) { // catch(myerror& err) { + + cout << "catch err in main" << endl; + cout << &err.s << endl; + cout << err.s << endl; + } + + return 0; +} +``` + + + +```cpp +outer begin +outer exception caught err +&err.s0x7fae4c405b48 : hello +catch err in main +0x7fae4c405b48 +hello +``` + + + +> 异常加引用后可以修改原函数的值 +> +> 如果不加引用会生成新的变量或者对象 + + + +指定函数不能抛出异常,调优函数的执行性能 + + + +## noexcept + + + +```cpp +#include +#include +#include +using namespace std; + +const char* foo() noexcept { // 检测到发出异常后,直接会被CPU干掉,即使外层有catch也捕获不到 + throw("123"); + return "noexcept"; +} + + +int main() { + + try { + cout << foo() << endl;// + } + catch(...) { + cout << "noexcept caught" << endl; + } + + + return 0; +} +``` + + + + + + + +```cpp +#include +#include +#include +#include +using namespace std; + +const char* foo() noexcept { // 检测到发出异常后,直接会被CPU干掉,即使外层有catch也捕获不到 + //throw("123"); + return "noexcept"; +} + +void Process(int v) { + if (v > 3) { + throw runtime_error("mumber too big"); + } +} + +int main() { + + vector v = {1, 2, 3, 4, 5}; + for (int i = 0; i < 5; i++) { + try { + Process(v[i]); + cout << v[i] << endl; + } catch(runtime_error &e) { + cout << e.what() << endl; + } + } + + return 0; +} +``` + + + +```cpp +1 +2 +3 +mumber too big +mumber too big +noexcept +``` + + + + + +Fault(DE, Division Error) 执行时触发,异常处理后返回当前语句 + + + +Trap(int 3)执行后触发,异常处理后返回的是下一句 + +Abort() 执行后直接宕机 + +异常本身是一个转移控制流的机制 + +缺页异常 + + + + + + + +系统栈有两个指针 + +爆栈:栈的上溢 + +栈溢出:栈的下溢,写数据超出了自己的范围 + + + +# 6.C++STL + +## 1.string + +```cpp +#include +#include +using namespace std; + +int main() { + string s = "abc"; + cout << s << endl; + s += "abc"; + cout << s << endl; + string s1 = "123"; + s += s1; + cout << s << endl; + string s3 = "222"; + s.append(s3); + cout << s << endl; + string s4 = s1 + s3; + cout << s4 << endl; + cout << s << endl; + cout << s.find("1") << endl; + + cout << s << endl; + s.replace(0, 2, "111"); + cout << s << endl; + + string s11 = "abcd"; + string s12 = "abcd"; + cout << s11.compare(s12) << endl; + //cout << s12 - s11 << endl; + if (s11.compare(s12) == 0) { + cout << "==" << endl; + } else if(s11.compare(s12) < 0){ + cout << "<" << endl; + } else { + cout << ">" << endl; + } + cout << "+====" << endl; + cout << s << endl; + cout << s.substr(1, 3) << endl; + cout << s << endl; + + cout << "+====" << endl; + cout << s << endl; + cout << s.insert(3, "kkk") << endl; + cout << s << endl; + s.erase(0, 2); + cout << s << endl; + + + return 0; +} + +``` + + + +## 2.vector + +```cpp +#include +#include +using namespace std; + +/// 初始化 +//vector v; /// 采用模板类实现。默认构造 +// vector(v.begin(), v.end()); // vector v1(arr, arr + sizeof(arr/ sizeof(int))); +// vector(n, elem) +// vector(const vector &vec); +// int arr[] = {2, 3, 4, 1, 9}; +void test01() { + vector v1; + int arr[] = {10, 20, 30, 40}; + vector v2(arr, arr + sizeof(arr) / sizeof(int)); + vector v3(v2.begin(), v2.end()); + vector v4(v2); + for (vector::iterator it = v4.begin(); it != v4.end(); ++it) { + cout << (*it) << endl; + } + return ; +} +// 赋值 +void test02() { + int arr[] = {10, 20, 30, 40}; + vector v1(arr, arr + sizeof(arr) / sizeof(int)); + + vector v2; + v2.assign(v1.begin(), v1.end()); + + for (vector::iterator it = v2.begin(); it != v2.end(); ++it) { + cout << (*it) << endl; + } + return ; +} + +// 大小操作 +void test3() { + int arr[] = { 100, 200, 300, 400}; + vector v(arr, arr + sizeof(arr) / sizeof(int)); // 默认构造 + cout << "size : " << v.size() << endl; + if (v.empty()) { + cout << "空!" << endl; + } else { + cout << "不空" << endl; + } + for (auto it : v) cout << it << " "; + cout << endl; + v.resize(2); // 扔掉后面的 + for (auto it : v) cout << it << " "; + cout << endl; + v.resize(6, 1); // 增加后面的, 默认1 + for (auto it : v) cout << it << " "; + cout << endl; + + cout << "size :" << v.size() << endl; + cout << "容量capacity :" << v.capacity() << endl; + + return ; +} + +// 存取数据 +void test4() { + int arr[] = { 100, 200, 300, 400}; + vector v(arr, arr + sizeof(arr) / sizeof(int)); // 默认构造 + cout << "size : " << v.size() << endl; + for (int i = 0; i < v.size(); i++) { + cout << v[i] << " "; + } + cout << endl; + // cout << v[v.size()] << endl; // 不会抛出异常 + for (int i = 0; i < v.size(); i++) { + cout << v.at(i) << " "; + } + cout << endl; + // cout << v.at(v.size()) << endl;// 会抛出异常 + + cout << "front : " << v.front() << endl; + cout << "back : " << v.back() << endl; + + return ; +} + +// 插入和删除 +void test5() { + vector v; + v.push_back(10); + v.push_back(20); + + // 头插法 + v.insert(v.begin(), -1); + v.insert(v.end(), 30); + v.insert(v.begin() + 2, 222); // vector支持随机访问 + // 支持数组下表,一般都支持随机访问 + // 迭代器可以直接 + 2 + 3 -2 -5操作 + for (auto it : v) cout << it << " "; + cout << endl; + + // 删除 + v.erase(v.begin()); + for (auto it : v) cout << it << " "; + cout << endl; + v.erase(v.begin() + 1, v.end()); + for (auto it : v) cout << it << " "; + cout << endl; + v.clear(); + cout << "size : " << v.size() << endl; + +} + +// 巧用swap缩减空间 +void test6() { + // vector添加元素 他会自动增长, 你删除的元素的时候,他不会自动减少 + vector v; + for (long long i = 0; i < 10000000; i++) { + v.push_back(i); + } + cout << "size : " << v.size() << endl; + cout << "capacity :" << v.capacity() << endl; + + + v.resize(10); + cout << "=======" << endl; + cout << "size : " << v.size() << endl; + cout << "capacity :" << v.capacity() << endl; + + vector(v).swap(v); + cout << "=======" << endl; + cout << "size : " << v.size() << endl; + cout << "capacity :" << v.capacity() << endl; +} + +// +void test7() { + // resverse 预留空间 resize 区别 + vector v; + int num = 0; + int *address = NULL; + v.reserve(100000); + for (int i = 0; i < 10000000; i++) { + v.push_back(i); + if (address != &(v[0])) { + address = &(v[0]); + num++; + } + } + cout << "num : " << num << endl; + // 如果知道需要的空间,可以用reverse预留空间,减少拷贝新空间的时间 + +} + + +int main() { + + test7(); + + return 0; +} + +``` + + + +## 3.dequeue + +```cpp +#include +#include +using namespace std; + +//dequeue 双端队列, +// 可以从尾部插入,删除push_bck,pop_back +// 可以从头部插入,删除 pop_front, push_front +// 和vector一样,支持随机存储,双向开口的连续性空间,在头部插入常数 +// 动态 连续空间 随时可以增加一端新的空间,没有容器的概念 +// 插入删除元素效率高 +// 指定位置插入会导致元素移动,降低效率 +// 支持随机存取,效率高 + +// 初始化 +void test1() { + deque d; + deque d2(10, 5); + deque d3(d2.begin(), d2.end()); + deque d4(d3); + + for (auto it : d4) { + cout << it << " "; + } + cout << endl; + +} + + +// 赋值 初始化 +void test2() { + deque d; + deque d2; + deque d3; + d.assign(10, 2); + + for (int it : d) { + cout << it << " "; + } + cout << endl; + + d2.assign(d.begin(), d.end()); + for (int it : d2) { + cout << it << " "; + } + cout << endl; + + // assign(beg, end)将[beg, end) 区间中的数据拷贝赋值给本身 + // assign(n, elem); 将n个elem拷贝赋值给本身 + + d3 = d2;// 等号赋值 + for (int it : d3) { + cout << it << " "; + } + cout << endl; + + // 自动清 0,在缓冲区排序 + + d.swap(d2); // 交换两个空间的元素 + + + if (d.empty()) { // 判断容器是否为空 + cout << "空 !" << endl; + } else { + cout << "非空!" << endl; + } + cout << endl; + d.resize(5); // 10个元素,后5个元素扔掉 + for (auto it : d) { + cout << it << " "; + } + cout << endl; +} + +void test3() { + // deque插入和删除 + deque d1; + d1.push_back(100); + d1.push_front(200); + d1.push_back(200); + d1.push_back(300); + d1.push_front(300); + for (deque::iterator it = d1.begin(); it != d1.end(); ++it) { + cout << *it << " "; + } + cout << endl; + int val = d1.front(); + d1.pop_front(); + cout << val << endl; + cout << d1.back() << endl; + d1.pop_back(); // 删除最后一个元素 + for (deque::iterator it = d1.begin(); it != d1.end(); ++it) { + cout << *it << " "; + } + return ; +} + +int main() { + test3(); + + return 0; +} + +``` + + + +## 4.stack + +```cpp +#include +#include +using namespace std; + +void test1() { + stack s, s2(s); + // stack操作 + s.push(10); + s.push(20); + s.push(30); + s.push(100); + //cout << "top : " << s.top() << endl; + while (!s.empty()) { + cout << "top : " << s.top() << endl; + s.pop(); + } + cout << "size :" << s.size() << endl; + +} +// 栈不能遍历,不支持随机存储 + +int main() { + test1(); + + return 0; +} + +``` + +## 5.queue + +```cpp +#include +#include +using namespace std; + +// queue 容器 队列容器 先进先出 + +void test() { + queue q, q2(q); + q.push(10); + q.push(20); + q.push(30); + q.push(40); + cout << "back : " << q.back() << endl; + + while (!q.empty()) { + cout << q.front() << endl; + q.pop(); + } + cout << "size : " << q.size() << endl; + + +} + +int main() { + test(); + + return 0; +} + +``` + +## 6.list + +```cpp +#include +#include +using namespace std; + +// 链表 是由一系列的结点组成,结点包含两个域,一个数据域,一个指针域 +// 链表内存是非连续的, +// 添加删除效率高, 时间复杂度是常数,不需要移动元素,比数组添加删除效率高 +// 链表只有在需要的时候,才分配内存 +// 需要额外的空间保存结点关系, 有前驱和后继 + +// 初始化 + +void test() { + list l; + list l2(10, 10); // 有参构造 + list l3(l2); // 拷贝构造 + list l4(l3.begin(), l3.end()); + + for (auto i : l4) { + cout << i << " "; + } + cout << endl; + +} +void test1() { + list l1; + l1.push_back(100); + l1.push_front(200); + for (auto i : l1) { + cout << i << " "; + } + cout << endl; + + + l1.insert(l1.begin(), 300); + l1.insert(l1.end(), 300); + for (auto i : l1) { + cout << i << " "; + } + cout << endl; + + list::iterator it = l1.begin(); + l1.insert(++(++it), 500); + for (auto i : l1) { + cout << i << " "; + } + cout << endl; + l1.remove(300); // 删除匹配的所有值 + for (auto i : l1) { + cout << i << " "; + } + cout << endl; + + + l1.pop_back(); + l1.pop_front(); + + l1.erase(l1.begin(), l1.end()); + return ; + +} + +// 大小操作 赋值操作 +void test2() { + list l1; + l1.assign(10, 10); + list l2; + l2 = l1; + for (auto i : l2) { + cout << i << endl; + } + cout << endl; + l1.swap(l2); +} +// 反转 +void test4() { + list l1; + for (int i = 0; i < 10; i++) { + l1.push_back(i); + } + l1.reverse(); + for (auto i : l1) { + cout << i << " "; + } + cout << endl; +} +bool cmp(int x, int y) { + return x > y; +} +// 排序 +void test5() { + list l1; + l1.push_back(2); + l1.push_back(1); + l1.push_back(5); + l1.push_back(9); + + for (auto i : l1) { + cout << i << " "; + } + cout << endl; + l1.sort(); // 默认从小到大 + for (auto i : l1) { + cout << i << " "; + } + cout << endl; + + l1.sort(cmp); // 从大到小 + for (auto i : l1) { + cout << i << " "; + } + cout << endl; + + +} + +// sort仅支持可随机访问的容器 +// list不支持随机访问 + + +int main() { + test5(); + + return 0; +} + +``` + + + +## 7.set + +```cpp +#include +#include +using namespace std; +// set/multiset 插入的时候会自动排序 +// set 所有的值都是不相等的 +// multiset 可以有重复的值 +// 可以通过set迭代器可以改变元素的值吗? +// 不可以,删除结点再加上结点 + +// 初始化 自动排序 默认从小到大 拷贝构造 + +// 仿函数 +class mycompare { +public : + bool operator()(int v1, int v2) { + return v1 > v2; + } +}; +void test1() { + + set s1; + s1.insert(7); + s1.insert(3); + s1.insert(2); + s1.insert(6); + s1.insert(4); + s1.insert(10); + s1.insert(2); + + for (set::iterator it = s1.begin(); it != s1.end(); ++it) { + cout << *it << " "; + } + cout << endl; + // 赋值操作 + set s2 = s1; + + // 删除操作 + s1.erase(s1.begin()); + s1.erase(7); + for (set::iterator it = s1.begin(); it != s1.end(); ++it) { + cout << *it << " "; + } + cout << endl; + + // cout << "====" << endl; + // // 改变默认排序 从大到小 + // set s3; + // s3.insert(7); + // s3.insert(3); + // s3.insert(2); + // s3.insert(6); + // s3.insert(4); + // s3.insert(10); + // s3.insert(2); + // for (set::iterator it = s1.begin(); it != s1.end(); ++it) { + // cout << *it << " "; + // } + // cout << endl; + +} + +// 查找 +void test2() { + // 实值 + set s1; + s1.insert(7); + s1.insert(3); + s1.insert(2); + s1.insert(6); + s1.insert(4); + s1.insert(10); + s1.insert(2); + + for (set::iterator it = s1.begin(); it != s1.end(); ++it) { + cout << *it << " "; + } + cout << endl; + set::iterator ret = s1.find(4); + if (ret == s1.end()) { + cout << "没有找到!" << endl; + } else { + cout << "ret : " << *ret << endl; + } + // 找到第一个>=key的元素 + ret = s1.lower_bound(11); + if (ret == s1.end()) { + cout << "没有找到" << endl; + } else { + cout << "ret : " << *ret << endl; + } + // 找到第一个大于key的值 + ret = s1.upper_bound(2); + if (ret == s1.end()) { + cout << "没有找到" << endl; + } else { + cout << "ret : " << *ret << endl; + } + + // equal_range返回 lower_bound 和 upper_bound 返回两个迭代器 + pair::iterator, set::iterator> myret = s1.equal_range(2); + if (myret.first == s1.end()) { + cout << "没有找到" << endl; + } else { + cout << "找到 lower_bound : " << (*myret.first) << endl; + } + if (myret.second == s1.end()) { + cout << "没有找到" << endl; + } else { + cout << "找到 upper_bound : " << (*myret.second) << endl; + } + +} +class Person { +public : + Person(int id, int age) : id(id), age(age) {} + +public : + int id; + int age; +}; +class cmp{ +public : + bool operator()(Person p1, Person p2) const { + return p1.age < p2.age; + } +}; +void test3() { + set s1; + Person p1(10, 50), p2(30, 40), p3(50, 60); + + s1.insert(p1); + s1.insert(p2); + s1.insert(p3); + + for (auto i : s1) { + cout << i.id << " " << i.age << endl;; + } + cout << endl; + + // 查找 + Person p4(10, 60); + set::iterator ret = s1.find(p4); + if (ret == s1.end()) { + cout << "没有找到" << endl; + } else { + cout << "找到 只会根据age查找,因为是根据age排序的:" << (*ret).id << " " << (*ret).age << endl; + } + +} + +int main() { + test3(); + + return 0; +} + +``` + +## 8.pair + +```cpp +#include +#include +using namespace std; + +// 对组 pair +void test() { + + + // 构造方法 + pair p(10, 20); + cout << p.first << " " << p.second << endl; + + pair p2 = make_pair(10, "guziqiu"); + cout << p2.first << " " << p2.second << endl; + + pair p3 = p2; +} + + +int main() { + test(); + + return 0; +} + +``` + + + +## 9.map + +```cpp +#include +#include +using namespace std; + +// map初始化 +void test1() { + map mymap; // map容器模板参数,第一个参数key的类型,第二个参数value的类型 + + // 插入数据 pair.first key值, pair.second value值 + // 1 + pair::iterator, bool> ret = mymap.insert(pair(10, 10)); + if (ret.second) { + cout << "第一次插入成功 " << endl; + } else { + cout << "插入失败" << endl; + } + + + + // 第二种 + ret = mymap.insert(make_pair(10, 20)); + if (ret.second) { + cout << "第二次插入成功 " << endl; + } else { + cout << "第二次插入失败" << endl; + } + + // 3 + ret = mymap.insert(map::value_type(30, 30)); + if (ret.second) { + cout << "第三次插入成功 " << endl; + } else { + cout << "插入失败" << endl; + } + // 4 + mymap[40] = 40; + mymap[50] = 5; + mymap[10] = 100; + // 如果发现key不存在,会创建pair插入到map的容器中 + // 如果发现key存在,那么会修改key对应的value + cout << mymap[90] << endl; + // 如果通过方括号的方式访问map中一个不存在的key, + // 那么map会将这个访问的key插入到map中,并进行默认初始化 + + // 打印 + for (map::iterator it = mymap.begin(); it != mymap.end(); ++it) { + cout << (*it).first << " " << (*it).second << " ++ "; + cout << it->first << " " << it->second << endl; + } + cout << endl; + for (auto i : mymap) { + cout << i.first << " " << i.second << endl; + } + +} + +class mykey { +public : + mykey(int ind, int id) { + this->mind = ind; + this->nid = id; + } + int mind; + int nid; +}; +struct cmp { + bool operator()(mykey k1, mykey k2) const { + return k1.mind < k2.mind; + } +}; +void test2() { + map mymap; + mymap.insert(make_pair(mykey(1,2), 10)); + mymap.insert(make_pair(mykey(4,5), 20)); + for (auto i : mymap) { + cout << i.first.mind << " " << i.first.nid << " = " << i.second << endl; + } +} + + +int main() { + test2(); + + return 0; +} + +``` + +## 10.multmap + +```cpp +#include +#include +using namespace std; + +int main() { + + + return 0; +} + +``` + + + +# 6.C++线程的简单使用 + +## 1.thread + +```cpp +#include +#include +using namespace std; + +void func(int x) { + for (int i = 0; i < x; i++) cout << i << endl; + return ; +} + +void add_one(int &x) { + x += 1; + return ; +} + +int main() { + + int n = 8; + //thread t1(func, 30); + //thread t2(func, 30); // 同时输出两个数子说明是多线程 + thread t1(add_one, ref(n)); // 传引用需要加& + t1.join(); + //t2.join(); + cout << "hello world" << endl; + cout << n << endl; // 9 + + return 0; +} + +``` + + + +```cpp +hello world +9 +``` + + + + + +## 2.bind + +```cpp +#include +#include +#include +using namespace std; + +void add_one(int &x) { + cout << "add_one function " << endl; + x += 1; + return ; +} + +class Task { +public : + template + Task(FUNCTION &&f, ARGS ...args) { + cout << "Task constructor" << endl; + this->f = bind(f, forward(args)...); // 参数和变量的绑定 + } + void run() { + f(); + } +private : + function f; + +}; + + +int main() { + int n = 8; + thread t1(add_one, ref(n)); + Task t2(add_one, ref(n)); + t1.join(); + t2.run(); + t2.run(); + t2.run(); + cout << n << endl; + + return 0; +} +``` + + + +```cpp +Task constructor +add_ont function +add_ont function +add_ont function +add_ont function +12 +``` + + + + + +bind将变量与参数绑定 + + + +> bind基本使用 + + + +```cpp +#include +using namespace std; + +void func(int x) { + cout << x << endl; + return ; +} + + +int main() { + + + func(123); + func(123); + func(123); + func(123); + func(123); + + auto f = bind(func, 123); + cout << "=========" << endl; + + f(); + f(); + f(); + f(); + f(); + + + + return 0; +} +``` + +```cpp +123 +123 +123 +123 +123 +========= +123 +123 +123 +123 +123 +``` + + + + + +> 将相关参数和相关函数绑定,封装出一个新的函数 + + + +## bind--placeholders参数占位符 + +```cpp +#include +using namespace std; + +void add(int a, int b) { + cout << a << " + " << b << endl; + return ; +} + +int main() { + + cout << "========" << endl; + auto add3 = bind(add, 3, std::placeholders::_1); // bind完成部分参数绑定, 预留槽位 + // placeholders 参数占位符 + // + add3(45); + add3(89); + add3(100); + + + return 0; +} +``` + + + +```cpp +3 + 45 +3 + 89 +3 + 100 +``` + + + +```cpp +#include +using namespace std; + +void add(int a, int b) { + cout << a << " + " << b << endl; + return ; +} + +int main() { + + cout << "========" << endl; + auto add3 = bind(add, 3, std::placeholders::_1); // bind完成部分参数绑定, 预留槽位 + // placeholders 参数占位符 + // + add3(45); + add3(89); + add3(100); + + cout << "===" << endl; + auto add2 = bind(add, std::placeholders::_1, std::placeholders::_2); + add2(3, 4); + + auto add4 = bind(add, std::placeholders::_2, std::placeholders::_1); + add4(3, 4); + // 参数占位符 + // bind(function, 1, 2) + // 1 : 新函数中第一个参数 + // 2 : 新函数第二个参数 + + + return 0; +} + +``` + + + +```cpp +======== +3 + 45 +3 + 89 +3 + 100 +=== +3 + 4 +4 + 3 +``` + + + + + + + + + + + + + + + +```cpp +#include +using namespace std; +void func(int x) { + for (int i = 0; i < x; i++) cout << i << endl; + return ; +} + + +void add(int a, int b) { + cout << a << " + " << b << endl; + return ; +} + +void test_ref(int &x) { + x = 1; + cout << "test_ref function" << endl; +} + +int main() { + + cout << "========" << endl; + auto add3 = bind(add, 3, std::placeholders::_1); // bind完成部分参数绑定, 预留槽位 + // placeholders 参数占位符 + // + add3(45); + add3(89); + add3(100); + + cout << "===" << endl; + auto add2 = bind(add, std::placeholders::_1, std::placeholders::_2); + add2(3, 4); + + auto add4 = bind(add, std::placeholders::_2, std::placeholders::_1); + add4(3, 4); + // 参数占位符 + // bind(function, 1, 2) + // 1 : 新函数中第一个参数 + // 2 : 新函数第二个参数 + + cout << "===" << endl; + + int n = 0; + // auto ref_func = bind(test_ref, n); // 0 0 + auto ref_func = bind(test_ref, ref(n)); // 显示性的调用引用 0 1 + cout << n << endl; + ref_func(); + cout << n << endl; + } +``` + + + +```cpp +======== +3 + 45 +3 + 89 +3 + 100 +=== +3 + 4 +4 + 3 +=== +0 +test_ref function +1 +``` + + + + + +## function和bind + +```cpp +#include +using namespace std; +void func(int x) { + for (int i = 0; i < x; i++) cout << i << endl; + return ; +} + + +void add(int a, int b) { + cout << a << " + " << b << endl; + return ; +} + +void test_ref(int &x) { + x = 1; + cout << "test_ref function" << endl; +} + +int main() { + + // function高级函数指针对象 可以指向任意可调用对象 bind一般配合function使用 + // 返回void类型,()没有参数 + // y一个负责绑定函数,一个负责返回值类型 + function f = bind(func, 3); + f(); + + cout << "===" << endl; + function add5 = bind(add, 3, std::placeholders::_1); + add5(3); + add5(45); + + function add6 = bind(add, std::placeholders::_2, std::placeholders::_1); + add6(3, 4); + + cout << "===" << endl; + + int n = 0; + function ref_func1 = bind(test_ref, ref(n)); // 显示性的调用引用 0 1 + cout << n << endl; + ref_func1(); + cout << n << endl; + + cout << "=========" << endl; + auto add8 = bind(add, std::placeholders::_4, std::placeholders::_1); + add8(1, 2, 3, 4); + + + + + return 0; +} +``` + + + +```cpp +0 +1 +2 +=== +3 + 3 +3 + 45 +4 + 3 +=== +0 +test_ref function +1 +========= +4 + 1 +``` + + + +## function模板类的封装 + + + +```cpp +#include +#include +using namespace std; + +namespace guziqiu { + +template +class base { +public : + virtual T run(ARGS...) = 0; + virtual base *getCopy() = 0; + virtual ~base() {} +}; + +template +class normal_func : public base {// base 派生类 +public : + typedef T (*FUNC_T)(ARGS...); + normal_func(FUNC_T func) : func(func) {} + T run(ARGS ...args) override { + return func(forward(args)...); + } + base *getCopy() override { + return new normal_func(func); + } + +private: + FUNC_T func; +}; + +template +class functor : public base{// base 派生类 +public : + functor(C obj) : obj(obj) {} + T run(ARGS ...args) override { + return obj(forward(args)...); + } + base *getCopy() override { + return new functor(obj); + } + +private : + C obj; +}; + + +template class function; +template // 模板类的偏特化版本 +class function { +public : + function(T (*ptr)(ARGS...)) : fptr(new normal_func(ptr)) {} // 兼容普通指针类型 + template + function(C object) : fptr(new functor(object)) {} + + function(const function &obj) { + cout << "copy constructor" << endl; + this->fptr = obj.fptr->getCopy();// 深拷贝 + } // 拷贝构造 + + function(function &&obj) : fptr(obj.fptr){ + this->fptr = obj.fptr; + } // 移动构造 + + function &operator=(function &obj) { // 左值赋值运算符 + if (this == &obj) return *this; + if (this->fptr) delete this->fptr; + this->fptr = obj.fptr->getCopy(); + return *this; + } + function &operator=(function &&obj) { // 右值赋值运算符 + if (this == &obj) return *this; + this->fptr = obj.fptr; + obj.fptr = nullptr; + return *this; + } + + T operator()(ARGS ...args) { + return fptr->run(forward(args)...); + } + ~function() { + if (fptr != nullptr) delete fptr; + fptr = nullptr; + } +private : + base *fptr; +}; + +} // end of guziqiu + +void func(int a, int b) { + cout << "normal function" << endl; + return ; +} + +class ADD_FUNC { +public : + ADD_FUNC() : x(0) {} + void operator()(int a, int b) { + x += a + b; + cout << "class object : " << x << endl; + return ; + } + int x; +}; + +int main() { + ADD_FUNC add; + function F1 = func; + function F2 = add; + F1(3, 4); + F2(3, 4); + F2(5, 6); + + guziqiu::function f1 = func; + guziqiu::function f2 = add; + f1(3, 4); + f2(3, 4); + f2(5, 6); + + guziqiu::function f3(f2); + f3(7, 8); + f3 = f1; + f3(7, 8); + + return 0; +} + +``` + +```cpp +normal function +class object : 7 +class object : 18 +normal function +class object : 7 +class object : 18 +copy constructor +class object : 33 +normal function +``` + + + +## random随机函数的实现 + +```cpp +#include +using namespace std; + +namespace guziqiu { + static int __seed = 3; + void srand(int seed) { + __seed = seed; + return ; + } + int rand() { + return __seed = __seed * 3 % 101; // 只能保证等改率输出101 + } +} // end of guziqiu + + +int main() { + guziqiu::srand(time(0)); + for (int i = 0; i < 10; i++) { + cout << guziqiu::rand() << endl; + } + + return 0; +} + +``` + + + + + + + +进程:操作系统分配资源的最小单位 + +多个线程 + + + +线程:消费者, + +添加任务的线程:生产者 + + + +## thread线程池的简单实现 + + + +```cpp +#include +#include +#include +#include +#include +#include +#include // 条件型号量 + +namespace guziqiu { + +class Task { // 任务队列 +public : + template + Task(FUNCTION &&func, ARGS ...args); + void run(); + +private : + std::function func; +}; + + +class ThreadPool { +public : + ThreadPool(int n = 5); + template + void addOneTask(FUNCTION &&, ARGS...); + void stop(); // 等待线程池执行结束 +private : + std::vector threads; + std::queue tasks; + std::unordered_map running; + std::mutex m_mutex; // 互斥锁 + std::condition_variable m_condition; // 条件信号量 + + void workerThread(); // 入口函数 + Task *getOneTask(); + void stopThread(); // 毒药任务 +}; + + +template +Task::Task(FUNCTION &&func, ARGS ...args) { + this->func = std::bind(func, std::forward(args)...); +} +void Task::run() { + std::cout << "Task run : " << std::endl; + func(); + return ; +} + +ThreadPool::ThreadPool(int n) { + for (int i = 0; i < n; i++) { + threads.push_back(new std::thread(&ThreadPool::workerThread, this)); + } +} +template +void ThreadPool::addOneTask(FUNCTION &&func, ARGS... args) { + std::unique_lock lock(m_mutex); // 抢占互斥锁 + tasks.push(new Task(func, std::forward(args)...)); // 添加任务 + m_condition.notify_one(); // 通知其他线程已经有任务了 +} +void ThreadPool::workerThread() { // 入口函数 + std::thread::id id = std::this_thread::get_id(); + // std::cout << id << std::endl; + running[id] = true; + while (running[id]) { + Task *t = this->getOneTask(); // 取任务 + t->run();// 执行任务 + delete t; // 销毁任务 + } + return ; +} +Task *ThreadPool::getOneTask() { + // 取任务 + std::unique_lock lock(m_mutex);// 抢占互斥锁 + while (tasks.empty()) { // 当任务队列为空 + m_condition.wait(lock); // 等待任务 + } + Task *t = tasks.front(); + tasks.pop(); + + return t; +} + +void ThreadPool::stop() { + for (int i = 0; i < threads.size(); i++) { + this->addOneTask(&ThreadPool::stopThread, this); // 添加毒药任务 + } + + for (auto t : threads) { + t->join(); // 等待线程结束 + } + return ; +} + +void ThreadPool::stopThread() { + std::thread::id id = std::this_thread::get_id(); + running[id] = false; + return ; +} + +} // ed of guziqiu + + +void func(int a, int b, int c) { + + std::cout << "func id : " << a << ", "<< a + b + c << std::endl; + return ; +} + +int main() { + + guziqiu::Task t1(func, 1, 2, 3); + guziqiu::ThreadPool tp; + //std::cout << "hello, world" << std::endl; + //t1.run(); + + // 在线程池中添加100个任务 + for (int i = 0; i < 100; i++) { + tp.addOneTask(func, i, 2 * i, 3 * i); + } + tp.stop(); // 等待线程池中的任务执行结束 + + + return 0; +} +``` + + + + + +0321,2:21 + + + + + + + + + + + + + + + + + + + + + + + + + + + +# C++与游戏开发COCOS + +底层是由C/C++写出来的,开发的是图形库 + +引擎架构是树形结构 + +树形结构由一个导演进行维护 + +树形结构是中序遍历的,遍历场景 + +场景里面有动作: + +通过引用计数实现内存自动回收 + + + +![截屏2021-03-30 下午5.27.04](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-03-30%20%E4%B8%8B%E5%8D%885.27.04.png) + + + +# C++2048 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +# 0.end + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +# ERROE + +## 1.undefined reference to `std::cout' + +使用 g++编译期 + +增加using std::out; + + + +## 2.Undefined symbols for architecture x86_64 + + + +有方法为定义 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git "a/02.c++\347\254\224\350\256\260/09.\351\235\242\350\257\225\347\254\224\350\257\225\347\256\227\346\263\225\344\270\212.md" "b/02.c++\347\254\224\350\256\260/09.\351\235\242\350\257\225\347\254\224\350\257\225\347\256\227\346\263\225\344\270\212.md" new file mode 100644 index 0000000..bea83ff --- /dev/null +++ "b/02.c++\347\254\224\350\256\260/09.\351\235\242\350\257\225\347\254\224\350\257\225\347\256\227\346\263\225\344\270\212.md" @@ -0,0 +1,407 @@ +# 1.编码能力提升 + +# 2.二分专题 + +# 3.OJ题目讲解 + +# 4.leetcode题目 + +# 5.STL容器使用与练习 + +## 5.1queue与stack的操作 + +### 使用queue + +```cpp +queue que; +que.push(5);//入队向队列添加5 +que.pop();//出队 +que.front();//队首 +que.size();//队列中的元素个数 +que.empty();//判断队列是否为空,true false +``` + + + +### stack + +```cpp +stack s; +s.push(); +s.pop(); +s.top();//获得栈顶元素 +s.size(); +s.empty(); +``` + + + +底层是由双端队列实现的,本质上不是容器, + + + +## 5.2queue代码演示 + +```cpp +#include +#include + +using namespace std; +struct Node { + int x, y; +}; + +int main() { + queue que; + for (int i = 9; i > 4; i--) { + que.push(i); + } + + while (!que.empty()) { + //queue.size(); + cout << "que.front() = " << que.front() << "\t que.size() = " << que.size() << endl; + que.pop(); + } + queue q; + Node node; + node.x = 1; + node.y = 2; + q.push(node); + q.push((Node){7, 8}); + cout << q.front().x << endl;//1 + q.pop(); + Node temp = q.front(); + cout << temp.x << " " << temp.y << endl; + + return 0; +} +``` + + + +``` +que.front() = 9 que.size() = 5 +que.front() = 8 que.size() = 4 +que.front() = 7 que.size() = 3 +que.front() = 6 que.size() = 2 +que.front() = 5 que.size() = 1 +1 +7 8 +``` + + + +## 5.3stack代码演示 + +```cpp +#include +#include +using namespace std; + +int main() { + + stack s; + for (int i = 9; i > 4; i--) { + s.push(i); + } + while (s.size()) { + //!s.empty() + cout << "s.top() = " << s.top() << "\t s.size() = " << s.size() << endl; + s.pop(); + } + + + + return 0; +} +``` + +```shell +s.top() = 5 s.size() = 5 +s.top() = 6 s.size() = 4 +s.top() = 7 s.size() = 3 +s.top() = 8 s.size() = 2 +s.top() = 9 s.size() = 1 +``` + + + +## 5.4vector()动态数组priority_queue()优先队列 + +```cpp +vector v; +//初始化 +v(2);//2个 +v(1,2);//1个2 +v.push_back(5);//在结尾加入5,O(1) +v.size();// +v.insert(1, 6);//第1个位置插入6,O(n) +vector > v1;//二维数组,C++11之前必须有空格 +v(2); +v(1,vector2); + +``` + + + +底层 + + + +```cpp +priority_queue que;//默认是大的 +que.push(3); +que.pop(); +que.top();//堆顶元素 +que.size(); +que.empty(); +struct Node {//自定义序列无法排序,需要重载小于号 + int x, y; + bool operator<(const Node b) { + return this.x>b.x; +} +}; + + +``` + +底层堆实现 + + + +## 5.5vector()代码演示 + +```cpp +#include +#include +using namespace std; + +int main() { + vector v; + for (int i = 105; i <= 110; i++) { + v.push_back(i); + cout << "v.size() = "<< v.size() << endl; + } + for (int i = 0; i < v.size(); i++) { + printf("v[%d] = %d\n", i, v[i]); + } + cout << "==========" << endl; + + vector > v1; + v1.push_back(vector());//插入一个空的vector() + for (int i = 66; i <= 70; i++) { + v1[0].push_back(i);//在第一行插入 + } + v1.push_back(vector(5, 55));// 5个值为55的元素 + + vector v2; + v2.push_back(5); + v2.push_back(6); + v2.push_back(7); + v2.push_back(8); + + v1.push_back(v2); + v1.push_back(vector(10, 1)); + + for (int i = 0; i < (int)v.size(); i++) {//打印数组 + //整体作为二维数组输出 + for (int j = 0; j < (int)v1[i].size(); j++) { + printf("v1[%d][%d] = %d ",i, j, v1[i][j]); + //cout << v1[i][j] << "\t"; + } + cout << endl; + } + + return 0; +} + +``` + + + +``` +v.size() = 1 +v.size() = 2 +v.size() = 3 +v.size() = 4 +v.size() = 5 +v.size() = 6 +v[0] = 105 +v[1] = 106 +v[2] = 107 +v[3] = 108 +v[4] = 109 +v[5] = 110 +========== +v1[0][0] = 66 v1[0][1] = 67 v1[0][2] = 68 v1[0][3] = 69 v1[0][4] = 70 +v1[1][0] = 55 v1[1][1] = 55 v1[1][2] = 55 v1[1][3] = 55 v1[1][4] = 55 +v1[2][0] = 5 v1[2][1] = 6 v1[2][2] = 7 v1[2][3] = 8 +v1[3][0] = 1 v1[3][1] = 1 v1[3][2] = 1 v1[3][3] = 1 v1[3][4] = 1 v1[3][5] = 1 v1[3][6] = 1 v1[3][7] = 1 v1[3][8] = 1 v1[3][9] = 1 + +``` + +## 5.6priority_queue()代码演示 + +```cpp +#include +#include +using namespace std; + +struct Node { + int x, y; + double z; + bool operator< (const Node &b) const{//重载<,默认使用重载小于号,但是小于号内部可以实现各种各样符合需求的功能 + return this->x < b.x;//按照x从大到小 + //return this->x > b.x;//从大到小 + } +}; + + +int main() { + //自定义结构 + cout << "priority_queue" << endl; + priority_queue que1; + que1.push((Node){1, 2, 4.5}); + que1.push((Node){2, 1, 5.6}); + cout << que1.top().x << endl; + que1.pop(); + cout << que1.top().x << endl; + + + cout << "priority_queue" << endl; + priority_queue que; + que.push(10); + que.push(20); + que.push(5); + que.push(6); + que.push(1); + que.push(18); + while (!que.empty()) { + cout << que.top() << endl; + que.pop(); + } + + + + return 0; +} +``` + +``` +priority_queue +2 +1 +priority_queue +20 +18 +10 +6 +5 +1 +``` + + + +## 5.7string() + +```cpp +string 字符串 +str.size(); +str.length();//长度 +str.find(s, x);//在str中从x位置开始查找s,x默认为0 +str.insert(x, s2);//在第x位置插入s2 +str.substr(2);//2:截取长度, +str.replace(x, y, s2);//从x位置开始替换y个字符,替换成s2 +``` + +## 5.8map() + +```cpp +map 键值对 映射关系 +map n; +[]//m["123"] = 456;cout << m["123"] << endl;访问一个不存在的,就会默认加一个,值为默认值 +insert(); +find(); +earse(); +cout();// +底层:红黑树 +按照键排序 +multimap; +unordered_map();//hash表,C++11 + + +``` + + + +## 5.9map代码演示 + +```cpp +#include +#include +#include +using namespace std; +#include + +struct Node { + int x, y; + bool operator< (const Node &b) const { + return this->x > b.x; + } +}; + + +int main() { + cout << "map" << endl; + map m; + string a = "123"; + m[a] = 999; + cout << m[a] << endl; + cout << m["123"] << endl; + cout << m["456"] << endl; + + cout << "map " << endl; + map map1; + Node node; + map1[node] = 5; + + cout << "unordered_map" << endl; + map map2; + string a2 = "123"; + map2[a2] = 999; + cout << map2[a2] << endl; + cout << map2["123"] << endl; + cout << map2["456"] << endl; + + return 0; +} +``` + +``` +map +999 +999 +0 +map +unordered_map +999 +999 +0 +``` + + + + + +## 5.10set pair + + + + + +# 6.排列组合与搜索走地图问题 + +# 7.搜索综合问题 + + + diff --git "a/02.c++\347\254\224\350\256\260/10.\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234.md" "b/02.c++\347\254\224\350\256\260/10.\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234.md" new file mode 100644 index 0000000..6258f75 --- /dev/null +++ "b/02.c++\347\254\224\350\256\260/10.\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234.md" @@ -0,0 +1,1322 @@ + + + +# 9.计算机网络概论 + +![截屏2021-02-03 下午9.51.43](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-03%20%E4%B8%8B%E5%8D%889.51.43.png) + +![截屏2021-02-03 下午9.52.32](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-03%20%E4%B8%8B%E5%8D%889.52.32.png) + + + +![截屏2021-02-03 下午10.07.17](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-03%20%E4%B8%8B%E5%8D%8810.07.17.png) + +封装: + +![截屏2021-02-03 下午10.12.37](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-03%20%E4%B8%8B%E5%8D%8810.12.37.png) + +![截屏2021-02-03 下午10.06.55](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-03%20%E4%B8%8B%E5%8D%8810.06.55.png) + +![截屏2021-02-03 下午10.15.28](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-03%20%E4%B8%8B%E5%8D%8810.15.28.png) + +![截屏2021-02-03 下午10.20.09](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-03%20%E4%B8%8B%E5%8D%8810.20.09.png) + + + + + + + +语义:某个句子是什么意思 + +语法: + +同步:事件发生的顺序有迹可循,eg.三次挥手,具体都做什么 + + + + + +## 分组交换 + +分组 + +分包(packet)有路由协议决定 + + + + + +![截屏2021-02-03 下午10.31.51](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-03%20%E4%B8%8B%E5%8D%8810.31.51.png) + + + + + + + +![截屏2021-02-03 下午10.42.08](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-03%20%E4%B8%8B%E5%8D%8810.42.08.png) + +转发表:进来的数据计算要去的地方 + + + +## 电路交换 + +![截屏2021-02-03 下午10.44.20](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-03%20%E4%B8%8B%E5%8D%8810.44.20.png) + +接线员 + + + + + + + +![截屏2021-02-03 下午10.46.27](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-03%20%E4%B8%8B%E5%8D%8810.46.27.png) + + + +FDM + + + + + + + +![截屏2021-02-03 下午10.46.49](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-03%20%E4%B8%8B%E5%8D%8810.46.49.png) + + + + + +## 分组交换与电路交换的对比 + +分组:提供更好的带宽共享,成本低, + + + +## 时延掉包吞吐率 + +![截屏2021-02-03 下午10.49.59](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-03%20%E4%B8%8B%E5%8D%8810.49.59.png) + +结点处理时延:处理包的时间 + +排队时延:进入端口,等待处理的时间 + +传输时延:一个包发到网络上的时间 + +传播时延:信号经由主机出去,到达对方主机后,再回来经历的传输介质中传输的时间 + + + + + +![截屏2021-02-03 下午10.56.00](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-03%20%E4%B8%8B%E5%8D%8810.56.00.png) + +什么情况下丢包越来越严重 + +a分组到达队列的平均速率,所有分组都由Lbit构成,R为传输速率,La/R--->1 + +单位时间内到达的比特率 + + + + + +吞吐量 + + + + + +![截屏2021-02-03 下午10.58.51](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-03%20%E4%B8%8B%E5%8D%8810.58.51.png) + +取决于业务链路上传输速率最小的那个 + + + + + +# 10.应用层 + + + +应用层协议 + +![截屏2021-02-04 上午9.12.46](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-04%20%E4%B8%8A%E5%8D%889.12.46.png) + + + +P2P端到端:百度网盘 + +CS:王者荣耀、QQ. + +![截屏2021-02-04 上午9.13.50](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-04%20%E4%B8%8A%E5%8D%889.13.50.png) + +![截屏2021-02-04 上午9.15.18](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-04%20%E4%B8%8A%E5%8D%889.15.18.png) + + + + + +![截屏2021-02-04 上午9.17.29](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-04%20%E4%B8%8A%E5%8D%889.17.29.png) + +![截屏2021-02-04 上午9.29.03](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-04%20%E4%B8%8A%E5%8D%889.29.03.png) + + + + + +![截屏2021-02-04 上午9.29.56](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-04%20%E4%B8%8A%E5%8D%889.29.56.png) + +![截屏2021-02-04 上午9.30.31](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-04%20%E4%B8%8A%E5%8D%889.30.31.png) + + + +![截屏2021-02-04 上午9.30.49](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-04%20%E4%B8%8A%E5%8D%889.30.49.png) + +可靠:一定送到 + +吞吐量:大小 + +定时: + +安全性: + + + +![截屏2021-02-04 上午9.34.15](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-04%20%E4%B8%8A%E5%8D%889.34.15.png) + + + + + +![截屏2021-02-04 上午9.36.12](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-04%20%E4%B8%8A%E5%8D%889.36.12.png) + + + + + +![截屏2021-02-04 上午9.36.04](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-04%20%E4%B8%8A%E5%8D%889.36.04.png) + + + +![截屏2021-02-04 上午9.36.23](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-04%20%E4%B8%8A%E5%8D%889.36.23.png) + + + +![截屏2021-02-04 上午9.37.25](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-04%20%E4%B8%8A%E5%8D%889.37.25.png) + + + + + +## HTTP与WEB协议 + + + +![截屏2021-02-04 上午9.51.51](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-04%20%E4%B8%8A%E5%8D%889.51.51.png) + +![截屏2021-02-04 上午9.52.27](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-04%20%E4%B8%8A%E5%8D%889.52.27.png) + +URL统一资源定位符 + +web浏览器:解析资源 + +web服务器:tomcat等 + + + +![截屏2021-02-04 上午9.54.21](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-04%20%E4%B8%8A%E5%8D%889.54.21.png) + + + + + +![截屏2021-02-04 上午9.54.56](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-04%20%E4%B8%8A%E5%8D%889.54.56.png) + + + +![截屏2021-02-04 上午9.56.47](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-04%20%E4%B8%8A%E5%8D%889.56.47.png) + +![截屏2021-02-04 上午9.58.45](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-04%20%E4%B8%8A%E5%8D%889.58.45.png) + + + +**为什么是三次握手?不是两次?不是四次?** + + + +![截屏2021-02-04 上午10.00.33](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-04%20%E4%B8%8A%E5%8D%8810.00.33.png) + +![截屏2021-02-04 上午10.02.52](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-04%20%E4%B8%8A%E5%8D%8810.02.52.png) + + + + + +![截屏2021-02-04 上午10.03.04](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-04%20%E4%B8%8A%E5%8D%8810.03.04.png) + + + +![截屏2021-02-04 上午10.03.50](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-04%20%E4%B8%8A%E5%8D%8810.03.50.png) + +![截屏2021-02-04 上午10.06.07](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-04%20%E4%B8%8A%E5%8D%8810.06.07.png) + +![截屏2021-02-04 上午10.07.56](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-04%20%E4%B8%8A%E5%8D%8810.07.56.png) + + + +![截屏2021-02-04 上午10.15.34](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-04%20%E4%B8%8A%E5%8D%8810.15.34.png) + +![截屏2021-02-04 上午10.28.39](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-04%20%E4%B8%8A%E5%8D%8810.28.39.png) + + + +## HTTP协议和HTTPS协议 + +![截屏2021-02-05 下午1.37.44](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-05%20%E4%B8%8B%E5%8D%881.37.44.png) + +![截屏2021-02-05 下午1.38.09](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-05%20%E4%B8%8B%E5%8D%881.38.09.png) + +![截屏2021-02-05 下午1.41.05](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-05%20%E4%B8%8B%E5%8D%881.41.05.png) + +![截屏2021-02-05 下午1.43.47](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-05%20%E4%B8%8B%E5%8D%881.43.47.png) + + + +## FTP协议 + +在局域网传输 + +![截屏2021-02-05 下午1.45.36](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-05%20%E4%B8%8B%E5%8D%881.45.36.png) + + + +![截屏2021-02-05 下午1.47.03](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-05%20%E4%B8%8B%E5%8D%881.47.03.png) + + + +控制连接:传命令,指令,常连接 + +数据连接:传数据,短连接 + +为什么用两条:双连接使业务实现更加简单,eg:专线,指令确定什么时候结束,指令信息引发的数据传输不一定什么时候传输,需要预先判断,不确定下一步是命令还是数据,需要很步骤,如果有两个,不需要关注数据多大,不论文件多大,收到关闭结束就可以,性能得到提升, + +![截屏2021-02-05 下午1.59.27](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-05%20%E4%B8%8B%E5%8D%881.59.27.png) + + + + + +## SMTP、POP3、IMAP协议 + +![截屏2021-02-05 下午2.02.37](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-05%20%E4%B8%8B%E5%8D%882.02.37.png) + +使用TCP传输 + + + +## DNS协议 + +![截屏2021-02-05 下午2.17.10](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-05%20%E4%B8%8B%E5%8D%882.17.10.png) + +![截屏2021-02-05 下午2.27.31](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-05%20%E4%B8%8B%E5%8D%882.27.31.png) + +![截屏2021-02-05 下午2.35.24](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-05%20%E4%B8%8B%E5%8D%882.35.24.png) + +![截屏2021-02-05 下午2.36.43](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-05%20%E4%B8%8B%E5%8D%882.36.43.png) + + + +主机发送请求(1.guziqiu.com),先到本地DNS查找(没有),然后到根DNS查找(2.是顶级域名),回应去顶级域名查找(3.告知去com域名地址),顶级域名回应(4/5.告知到权威DNS查找),权威DNS阿里云维护(6/7告知IP地址),本地DNS存一份,为了下一次访问,(域名过期时间10min) + +本地递归,远程迭代 + +![截屏2021-02-05 下午3.11.25](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-05%20%E4%B8%8B%E5%8D%883.11.25.png) + +TTL域名有效时间:aliyun10min + + + + + + + + + + + +# 11.可靠传输协议的实现 + +## 运输层协议 + +![截屏2021-02-05 下午4.00.29](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-05%20%E4%B8%8B%E5%8D%884.00.29.png) + +![截屏2021-02-05 下午4.04.28](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-05%20%E4%B8%8B%E5%8D%884.04.28.png) + + + +![截屏2021-02-05 下午4.15.14](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-05%20%E4%B8%8B%E5%8D%884.15.14.png) + +![截屏2021-02-05 下午4.15.47](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-05%20%E4%B8%8B%E5%8D%884.15.47.png) + +![截屏2021-02-05 下午4.17.14](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-05%20%E4%B8%8B%E5%8D%884.17.14.png) + + + +## 为什么会选择无连接运输 + +![截屏2021-02-05 下午4.19.17](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-05%20%E4%B8%8B%E5%8D%884.19.17.png) + +UDP由自己控制,TCP有控制机制 + + + +## UDP + +![截屏2021-02-05 下午4.29.21](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-05%20%E4%B8%8B%E5%8D%884.29.21.png) + +![截屏2021-02-05 下午4.30.26](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-05%20%E4%B8%8B%E5%8D%884.30.26.png) + +![截屏2021-02-05 下午4.33.06](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-05%20%E4%B8%8B%E5%8D%884.33.06.png) + + + +## RDT可靠传输协议 + +信道可靠,上端不用考虑是否可靠,数据一定可靠 + +可靠的数据协议通过不可靠的信道 + +![截屏2021-02-05 下午4.36.31](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-05%20%E4%B8%8B%E5%8D%884.36.31.png) + +![截屏2021-02-05 下午4.37.10](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-05%20%E4%B8%8B%E5%8D%884.37.10.png) + + + +具有比特差错信道:能感知到比特级的错误 + +![截屏2021-02-05 下午4.39.20](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-05%20%E4%B8%8B%E5%8D%884.39.20.png) + +![截屏2021-02-05 下午4.43.41](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-05%20%E4%B8%8B%E5%8D%884.43.41.png) + +![截屏2021-02-05 下午4.44.56](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-05%20%E4%B8%8B%E5%8D%884.44.56.png) + +![截屏2021-02-05 下午4.46.51](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-05%20%E4%B8%8B%E5%8D%884.46.51.png) + +![](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-05%20%E4%B8%8B%E5%8D%884.46.51.png) + +![截屏2021-02-05 下午4.50.43](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-05%20%E4%B8%8B%E5%8D%884.50.43.png) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +# 12.TCP协议 + +## 12.1 报文 + +通过套接字使用底层提供给我们的服务 + +![截屏2021-02-05 下午5.17.37](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-05%20%E4%B8%8B%E5%8D%885.17.37.png) + +![截屏2021-02-05 下午5.18.30](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-05%20%E4%B8%8B%E5%8D%885.18.30.png) + + + +序号:字节流序号(非连续) + +确认号:希望得到的下一个正确得到的序号 + + + +首部长度4bit + +接收窗口的大小: + +URG:紧急数据, + +PSH:立即上交,收到数据立马上交应用层 + +ACK:确认码=1,对方发过来的确认 + +RST:连接远程服务器,拒绝连接,对方没有打开端口 + +SYN:同步,三次握手 + +FIN:四次挥手 + + + + + +![截屏2021-02-06 下午4.22.41](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-06%20%E4%B8%8B%E5%8D%884.22.41.png) + +用户键入x产生数据报,发送到服务器, + + + + + +![截屏2021-02-06 下午4.26.07](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-06%20%E4%B8%8B%E5%8D%884.26.07.png) + + + +![截屏2021-02-06 下午4.59.42](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-06%20%E4%B8%8B%E5%8D%884.59.42.png) + +主要针对双方 + + + +## 12.2 三次握手 + +![截屏2021-02-06 下午5.05.55](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-06%20%E4%B8%8B%E5%8D%885.05.55.png) + +为什么是三次握手: + +因为双方都能确定能发送能接收,如果是两次,客户端能发能收,服务端无法确定发送的消息能否被收到,四次也可以,但是多余 + + + +## 12.3 四次挥手 + +![截屏2021-02-06 下午5.10.57](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-06%20%E4%B8%8B%E5%8D%885.10.57.png) + + + +为什么要四次: + +客户端发了FIN,服务端没有返回ACK,服务端发送FIN,客户端没有回复ACK,半关闭连接,连接还在,生成一种特殊状态的套接字,有害的,占用套接字 + +为什么发起方多等待30s~2min,两个最大生命周期: + +1.避免ACK丢失,服务器会发送ACK,(有可能发送给对方的ACK丢失) + +2.避免刚断开连接,(四次挥手完成)随机新端口(和之前的一样,端口重用)跑起来之后,收到之前已经断开的端口发送的FIN包或者数据,新端口数据还没有发送完成就断开,所以需要四次挥手 + + + +## 12.4 TCP状态转换图 + +![截屏2021-02-06 下午5.23.48](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-06%20%E4%B8%8B%E5%8D%885.23.48.png) + +![截屏2021-02-06 下午5.29.26](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-06%20%E4%B8%8B%E5%8D%885.29.26.png) + + + +## 12.5 拥塞控制方法 + +![截屏2021-02-06 下午5.32.58](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-06%20%E4%B8%8B%E5%8D%885.32.58.png) + + + + + +![截屏2021-02-06 下午5.34.18](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-06%20%E4%B8%8B%E5%8D%885.34.18.png) + +资源管理信元:每32个信元有一个资源管理信元,返回拥塞状态 + + + + + +![截屏2021-02-06 下午5.40.49](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-06%20%E4%B8%8B%E5%8D%885.40.49.png) + + + +![截屏2021-02-06 下午5.42.32](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-06%20%E4%B8%8B%E5%8D%885.42.32.png) + + + + + +## 12.6 拥塞控制:慢启动 + +![截屏2021-02-06 下午5.45.03](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-06%20%E4%B8%8B%E5%8D%885.45.03.png) + +MMS最大报文段长度,TCP一次放的最大数据量 + +RTT往返时间 + +![截屏2021-02-06 下午5.48.47](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-06%20%E4%B8%8B%E5%8D%885.48.47.png) + + + +发送超时为什么会变成1MSS,合理吗,为什么 + +不合理, + +为什么3个冗余ACK快速重传:包出问题 + + + + + +![截屏2021-02-06 下午5.49.58](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-06%20%E4%B8%8B%E5%8D%885.49.58.png) + + + +TCP拥塞控制流程转换 + +![截屏2021-02-06 下午5.52.11](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-06%20%E4%B8%8B%E5%8D%885.52.11.png) + +慢启动:收到冗余ACK, + + + + + + + + + +# 13.IP地址 + + + +## 13.1 IP地址的基本结构 + +![截屏2021-02-24 下午5.53.10](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-24%20%E4%B8%8B%E5%8D%885.53.10.png) + + + +## 13.2 MAC地址 + +![截屏2021-02-24 下午8.34.52](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-24%20%E4%B8%8B%E5%8D%888.34.52.png) + + + +网卡:网络适配器 + +## 13.3 IP地址的分类 + +![截屏2021-02-24 下午8.40.58](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-24%20%E4%B8%8B%E5%8D%888.40.58.png) + + + +本地通讯靠吼,子网掩码来区分是否在一个子网中 + +广播: + + + +## 13.4 ARP协议 + +![截屏2021-02-24 下午8.55.17](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-24%20%E4%B8%8B%E5%8D%888.55.17.png) + + + +ARP:将IP地址转换为MAC地址 + + + +# 14 网络层 + + + +## 14.1 转发和路由选择 + +![截屏2021-02-24 下午9.03.04](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-24%20%E4%B8%8B%E5%8D%889.03.04.png) + + + +![截屏2021-02-24 下午9.06.14](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-24%20%E4%B8%8B%E5%8D%889.06.14.png) + + + + + +## 14.2 路由选择算法 + + + +![截屏2021-02-24 下午9.08.24](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-24%20%E4%B8%8B%E5%8D%889.08.24.png) + + + +路由选择算法决定整个网络范围内的端到端的路径选择 + + + +转发表决定本地路由器内部转发的出口 + + + +## 14.3 分组网络 + +![截屏2021-02-24 下午9.15.21](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-24%20%E4%B8%8B%E5%8D%889.15.21.png) + + + +虚拟电路网络 + +![截屏2021-02-24 下午9.20.18](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-24%20%E4%B8%8B%E5%8D%889.20.18.png) + + + +![截屏2021-02-24 下午9.20.55](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-24%20%E4%B8%8B%E5%8D%889.20.55.png) + + + +![截屏2021-02-24 下午9.26.29](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-24%20%E4%B8%8B%E5%8D%889.26.29.png) + + + +## 14.4 路由器 + +![截屏2021-02-24 下午9.29.32](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-24%20%E4%B8%8B%E5%8D%889.29.32.png) + + + +交换结构:将出入端口输出到输出端口 + +路由选择处理器:只负责和 其他的路由器或者上层的管理软件 交互 + + + +![截屏2021-02-24 下午9.34.09](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-24%20%E4%B8%8B%E5%8D%889.34.09.png) + +线路端接:将传入到端口的信号转换成电信号 + + + +![截屏2021-02-24 下午9.39.13](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-24%20%E4%B8%8B%E5%8D%889.39.13.png) + +内存式:效率最低 + +总线:电信号可能抵消 + +纵横式: + + + +![截屏2021-02-24 下午9.43.46](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-24%20%E4%B8%8B%E5%8D%889.43.46.png) + + + +排队:可能会发生数据丢失,丢包 + +排队时延不可控 + + + +## 14.5 网络层内容图 + +![截屏2021-02-24 下午9.46.02](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-24%20%E4%B8%8B%E5%8D%889.46.02.png) + + + + + +## 14.6 IP数据报 + +![截屏2021-02-24 下午9.49.14](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-24%20%E4%B8%8B%E5%8D%889.49.14.png) + + + +寿命TTL:经过一个路由器TTL-1,避免路由器找不到目的地,经过一个路由器TTL-1,最终消亡在网络中 + + + +## 14.7 DHCP + +![截屏2021-02-24 下午9.52.13](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-24%20%E4%B8%8B%E5%8D%889.52.13.png) + + + +连上wifi时发生了什么事情? + +接入wifi后,就可以和网络中的其他设备通信,MAC地址不变,通过MAC地址申请一个可以用的IP地址,接入网络后,通过广播dest请求地址,DHCP server收到请求后发送DHCP offer,可以有多个DHCP offer(多个DHCP服务器同时发送),接收offer,发送请求IP地址,DHCP server确定,所有的报文都是广播,所有本地的设备都会知道他的IP地址 + + + + + +## 14.8 网络地址转换NAT + + + +![截屏2021-02-24 下午10.04.00](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-24%20%E4%B8%8B%E5%8D%8810.04.00.png) + + + + + + + +# 15.ICMP协议 + +![截屏2021-02-26 下午4.54.49](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-26%20%E4%B8%8B%E5%8D%884.54.49.png) + +ping + + + +![截屏2021-02-26 下午4.55.13](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-26%20%E4%B8%8B%E5%8D%884.55.13.png) + +网络层ICMP尝试连接目的主机,但是无法到达 + + + +![截屏2021-02-26 下午4.56.46 1](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-26%20%E4%B8%8B%E5%8D%884.56.46%201.png) + + + +u - v 1跳 + + + +D路由器的路由表 + +![截屏2021-02-27 下午4.39.35](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-27%20%E4%B8%8B%E5%8D%884.39.35.png) + +D->W 下一跳是A,需要两跳 + +![截屏2021-02-27 下午4.46.53](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-27%20%E4%B8%8B%E5%8D%884.46.53.png) + +需要一定的时间才可以收到改变,出现更新不及时的现象,网络波动 + + + + + + + +开放最短优先 + +![截屏2021-02-27 下午4.48.15](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-27%20%E4%B8%8B%E5%8D%884.48.15.png) + +范围的同时都可以收到 + + + + + +![截屏2021-02-27 下午4.51.52](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-27%20%E4%B8%8B%E5%8D%884.51.52.png) + +边界网关协议BGP:在自治系统和自治系统之间建立一条扩展的会话,它是一跳真实的TCP连接,这个连接会一直同步两段路由器的信息(如3a,和1c交换他两所知道的路由信息,及时获取自治系统内部的信息,TCP长连接) + + + + + + + +# 重点:DHCP、NAT、IP数据报、路由器、转发表和转发 + + + + + +# 16.链路层 + +## 16.1网络适配器 + +![截屏2021-02-27 下午5.01.39](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-27%20%E4%B8%8B%E5%8D%885.01.39.png) + +主要设备:网络适配器 即 网卡 + + + +## 16.2链路层提供的服务 + +![截屏2021-02-27 下午5.01.51](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-27%20%E4%B8%8B%E5%8D%885.01.51.png) + +链路层也可以实现可靠交付 + + + +> 为什么我们不在数据链路层实现可靠交付? +> +> 如果在数据链路层实现可靠传输,那么在网络层、传输层、应用层也就实现了可靠传输, 不可靠主要是因为物理链路不可靠 +> +> 如果要在数据链路层实现可靠交付,就得超时重传、序号、确认ACK、差错检验,通信效率降低 + + + +## 16.3差错检测 + +![截屏2021-02-27 下午5.11.21](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-27%20%E4%B8%8B%E5%8D%885.11.21.png) + + + + + +![截屏2021-02-27 下午5.12.02](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-27%20%E4%B8%8B%E5%8D%885.12.02.png) + + + + + +![截屏2021-02-27 下午5.13.48](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-27%20%E4%B8%8B%E5%8D%885.13.48.png) + + + +## 16.4链路层协议集 + +![截屏2021-02-27 下午5.17.37](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-27%20%E4%B8%8B%E5%8D%885.17.37.png) + +链路层协议集:描述了链路层做了什么事情 + + + + + +## 16.5信道划分协议 + +![截屏2021-02-27 下午5.20.52](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-27%20%E4%B8%8B%E5%8D%885.20.52.png) + + + +## 16.6 信道划分 CDMA协议 + +![截屏2021-02-27 下午5.25.25](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-27%20%E4%B8%8B%E5%8D%885.25.25.png) + + + + + + + +![截屏2021-02-27 下午5.43.59](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-27%20%E4%B8%8B%E5%8D%885.43.59.png) + + + + + +![截屏2021-02-27 下午5.48.23](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-27%20%E4%B8%8B%E5%8D%885.48.23.png) + + + +两个发送者 + +![截屏2021-02-27 下午5.50.55](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-27%20%E4%B8%8B%E5%8D%885.50.55.png) + + + + + +## 16.7 随机接入协议 + +![截屏2021-02-27 下午6.01.52](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-27%20%E4%B8%8B%E5%8D%886.01.52.png) + + + +![截屏2021-02-28 下午7.37.17](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-28%20%E4%B8%8B%E5%8D%887.37.17.png) + + + +![截屏2021-02-28 下午7.39.10](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-28%20%E4%B8%8B%E5%8D%887.39.10.png) + + + + + +## 16.8 载波侦听多路访问 CSMA/CD + +![截屏2021-02-28 下午7.41.51](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-28%20%E4%B8%8B%E5%8D%887.41.51.png) + +![截屏2021-02-28 下午7.47.36](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-28%20%E4%B8%8B%E5%8D%887.47.36.png) + + + +## 16.9 轮流协议 轮询协议 令牌传递协议 + +![截屏2021-02-28 下午7.48.48](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-28%20%E4%B8%8B%E5%8D%887.48.48.png) + + + + + + + +![截屏2021-02-28 下午7.50.22](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-28%20%E4%B8%8B%E5%8D%887.50.22.png) + + + +## 16.10 以太网技术 + +![截屏2021-02-28 下午7.51.16](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-28%20%E4%B8%8B%E5%8D%887.51.16.png) + + + +## 交换机表 + +![截屏2021-02-28 下午8.03.40](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-02-28%20%E4%B8%8B%E5%8D%888.03.40.png) + +MAC地址,接口 + + + + + + + + + + + + + + + + + +# 17. + + + + + + + + + + + + + + + + + + + +# 0000000 + + + + + + + +# 0作业 + + + +## 0.1 作业1:ls实现 + +![截屏2021-01-05 上午11.11.26](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-01-05%20%E4%B8%8A%E5%8D%8811.11.26.png) + + + + + +``` + +``` + + + + + +## 0.2 ls作业->opendir() + +> opendir() +> +> readdir() +> +> closedir() +> +> ftell() +> +> readdir() + + + + + +man getpwpid getpwuid getgrgid + + + + + + + +0 + +锁:数据保护,互斥, + +![fc73bb52fe85013f9d2b4713814cfa29](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/fc73bb52fe85013f9d2b4713814cfa29.jpg) + + + +```c +#include "00.head.h" +//#define INS 5 + +char num_file[] = "./.num"; +char lock_file[] = "./.lock"; +struct Msg { + int now; + int sum; +}; + +struct Msg num; + +size_t set_num(struct Msg *msg) { + FILE *f = fopen(num_file, "w"); + if (f == NULL) { + fclose(f); + perror("fopen"); + return -1; + } + size_t nwrite = fwrite(msg, 1, sizeof(struct Msg), f); + fclose(f); + return nwrite; +} + +size_t get_num(struct Msg *msg) { + FILE *f = fopen(num_file, "r"); + if (f == NULL) { + fclose(f); + perror("fopen()"); + return -1; + } + size_t nread = fread(msg, 1, sizeof(struct Msg), f); + fclose(f); + return nread; +} + +void do_add(int end, int pid_num) { + while (1) { + FILE *lock = fopen(lock_file, "w"); + if (lock == NULL) { + perror("fopen()"); + exit(1); + } + flock(lock->_fileno, LOCK_EX); + if (get_num(&num) < 0) { + fclose(lock); + continue; + } + + if (num.now > end) { + fclose(lock); + break; + } + num.sum += num.now; + num.now++; + printf("the %dth child : now = %d, sum = %d\n", pid_num, num.now, num.sum); + set_num(&num); + flock(lock->_fileno, LOCK_UN); + fclose(lock); + } + +} + +int main(int argc, char *argv[]) { + + int opt, start = 0, end = 0, ins = 0; + while ((opt = getopt(argc, argv, "s:e:i:")) != -1) { + switch (opt) { + case 's': + start = atoi(optarg); + break; + case 'e': + end = atoi(optarg); + break; + case 'i': + ins = atoi(optarg); + break; + default: + fprintf(stderr, "Usage : %s -s start_num -e end_num -i\n", argv[0]); + exit(1); + } + } + printf("start = %d\n end = %d\n", start, end); + + num.now = 0; + num.sum = 0; + set_num(&num); + + pid_t pid; + int x = 0; + for (int i = 1; i <= ins; i++) { + if ((pid = fork()) < 0) { + perror("fork()"); + exit(-1); + } + if (pid == 0) {//孩子 + x = i; + break; + } + } + if (pid != 0) { + for (int i = 0; i < ins; i++) { + wait(NULL); + } + + //printf("I am father!\n"); + //sleep(100); + } else { + do_add(end, x); + } + get_num(&num); + printf("sum = %d\n", num.sum); + + return 0; +} +``` + +```shell +./a.out -s 0 -e 100 -i 10 [0] +start = 0 + end = 100 +the 1th child : now = 1, sum = 0 +the 7th child : now = 2, sum = 1 +the 1th child : now = 3, sum = 3 +the 7th child : now = 4, sum = 6 +the 1th child : now = 5, sum = 10 +the 7th child : now = 6, sum = 15 +the 1th child : now = 7, sum = 21 +the 7th child : now = 8, sum = 28 +the 1th child : now = 9, sum = 36 +the 7th child : now = 10, sum = 45 +the 1th child : now = 11, sum = 55 +the 7th child : now = 12, sum = 66 +the 7th child : now = 13, sum = 78 +the 7th child : now = 14, sum = 91 +the 1th child : now = 15, sum = 105 +the 7th child : now = 16, sum = 120 +the 1th child : now = 17, sum = 136 +the 7th child : now = 18, sum = 153 +the 1th child : now = 19, sum = 171 +the 2th child : now = 20, sum = 190 +the 1th child : now = 21, sum = 210 +the 1th child : now = 22, sum = 231 +the 3th child : now = 23, sum = 253 +the 1th child : now = 24, sum = 276 +the 3th child : now = 25, sum = 300 +the 1th child : now = 26, sum = 325 +the 3th child : now = 27, sum = 351 +the 1th child : now = 28, sum = 378 +the 3th child : now = 29, sum = 406 +the 1th child : now = 30, sum = 435 +the 6th child : now = 31, sum = 465 +the 1th child : now = 32, sum = 496 +the 3th child : now = 33, sum = 528 +the 1th child : now = 34, sum = 561 +the 6th child : now = 35, sum = 595 +the 1th child : now = 36, sum = 630 +the 3th child : now = 37, sum = 666 +the 1th child : now = 38, sum = 703 +the 6th child : now = 39, sum = 741 +the 1th child : now = 40, sum = 780 +the 2th child : now = 41, sum = 820 +the 1th child : now = 42, sum = 861 +the 2th child : now = 43, sum = 903 +the 2th child : now = 44, sum = 946 +the 3th child : now = 45, sum = 990 +the 2th child : now = 46, sum = 1035 +the 1th child : now = 47, sum = 1081 +the 2th child : now = 48, sum = 1128 +the 2th child : now = 49, sum = 1176 +the 1th child : now = 50, sum = 1225 +the 2th child : now = 51, sum = 1275 +the 1th child : now = 52, sum = 1326 +the 2th child : now = 53, sum = 1378 +the 1th child : now = 54, sum = 1431 +the 7th child : now = 55, sum = 1485 +the 7th child : now = 56, sum = 1540 +the 7th child : now = 57, sum = 1596 +the 1th child : now = 58, sum = 1653 +the 6th child : now = 59, sum = 1711 +the 1th child : now = 60, sum = 1770 +the 2th child : now = 61, sum = 1830 +the 1th child : now = 62, sum = 1891 +the 1th child : now = 63, sum = 1953 +the 1th child : now = 64, sum = 2016 +the 4th child : now = 65, sum = 2080 +the 1th child : now = 66, sum = 2145 +the 4th child : now = 67, sum = 2211 +the 1th child : now = 68, sum = 2278 +the 4th child : now = 69, sum = 2346 +the 1th child : now = 70, sum = 2415 +the 4th child : now = 71, sum = 2485 +the 1th child : now = 72, sum = 2556 +the 4th child : now = 73, sum = 2628 +the 1th child : now = 74, sum = 2701 +the 4th child : now = 75, sum = 2775 +the 4th child : now = 76, sum = 2850 +the 1th child : now = 77, sum = 2926 +the 4th child : now = 78, sum = 3003 +the 1th child : now = 79, sum = 3081 +the 3th child : now = 80, sum = 3160 +the 1th child : now = 81, sum = 3240 +the 2th child : now = 82, sum = 3321 +the 1th child : now = 83, sum = 3403 +the 1th child : now = 84, sum = 3486 +the 6th child : now = 85, sum = 3570 +the 6th child : now = 86, sum = 3655 +the 4th child : now = 87, sum = 3741 +the 3th child : now = 88, sum = 3828 +the 1th child : now = 89, sum = 3916 +the 2th child : now = 90, sum = 4005 +the 1th child : now = 91, sum = 4095 +the 2th child : now = 92, sum = 4186 +the 1th child : now = 93, sum = 4278 +the 3th child : now = 94, sum = 4371 +the 4th child : now = 95, sum = 4465 +the 6th child : now = 96, sum = 4560 +the 6th child : now = 97, sum = 4656 +the 6th child : now = 98, sum = 4753 +the 4th child : now = 99, sum = 4851 +the 6th child : now = 100, sum = 4950 +the 1th child : now = 101, sum = 5050 +sum = 5050 +sum = 5050 +sum = 5050 +sum = 5050 +sum = 5050 +sum = 5050 +sum = 5050 +sum = 5050 +sum = 5050 +sum = 5050 +sum = 5050 +``` + +flock + + + + + + + + + + + + + +# 0.end \ No newline at end of file diff --git "a/02.c++\347\254\224\350\256\260/11.C++\346\230\245\346\213\233\347\252\201\345\207\273\347\217\255.md" "b/02.c++\347\254\224\350\256\260/11.C++\346\230\245\346\213\233\347\252\201\345\207\273\347\217\255.md" new file mode 100644 index 0000000..1d2fb25 --- /dev/null +++ "b/02.c++\347\254\224\350\256\260/11.C++\346\230\245\346\213\233\347\252\201\345\207\273\347\217\255.md" @@ -0,0 +1,2264 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +0301 + + + +# 1. C语言基础 + + + +## 1.1 printf scanf + +循环读入的方式 + +```cpp +while(~scanf("%d", a)); +while(scanf("%d", a) != EOF); +while(scanf("%d", a) != -1); +``` + + + +读入待空格的字符串 + +```cpp +%s不能读入空格 +"%[^\n]s" +[]字符匹配集、"%[^\n]s":读入除\n以为所有的字符串 +但是循环读入会有问题 +getchar();强行吞掉缓冲区一个字符 + +``` + + + +```cpp +#include +using namespace std; + +int main() { + char str[100] = {0}; + scanf("%[^\n]s", str); + printf("%s\n", str); + + while (~scanf("%[^\n]s", str)) { + char a = getchar();//吞掉字符 + printf("a = (%c)\n", a); + printf("%s\n", str); + } + + return 0; +} + +``` + + + +> %d输出十进制下的数字 +> +> %c 一个字符,可以读入空格、\t等 +> +> %s 一个字符串,不能读入空格 +> +> ==>无法控制读入时建议使用%s +> +> %.2f小数点后两位 +> +> %g科学计数法输出,用于去掉多余的0 + + + +```cpp +#include +using namespace std; + +int main() { + char ch[100]; + int a; + + scanf("%d%c", &a, ch); + printf("a = %d, ch = %c\n", a, ch); + //12 2 + //a = 12, ch = P + getchar(); + + scanf("%d %c", &a, ch); + printf("a = %d, ch = %c\n", a, ch); + getchar(); + + //正确示例 + scanf("%d%s", &a, ch); + printf("a = %d, ch = %c\n", a, ch[0]); + return 0; +} + + +``` + + + +## 1.2 C语言基本运算符 + +位运算由CPU直接计算,运算速度很快,%最慢 + +```cpp +x % 2 == x & 1 + +x % 4 == x & 3 + +x % 8 == x & 7 +2^4 + 2^3 + 2^2 + 2^1 + 2^0 +x % 6 == x & 5??? 不能保证二进制的每一位是6的倍数 + +x % 16 == x & 15 + +``` + + + +大整数,存储为字符串,按位与1, + +补码 = 反码 + 1 + +反码 = 原码按位取反 = ~原码 + +-1 = 1111…(32个) + + + +位运算:&、|、^、~ + +^异或, 相同为0,不同为1,是一类逆运算,和自己互为逆运算 + +逆运算:减法是加法是逆运算,加法不是减法是逆运算 + +逆运算满足交换律(a - b = c, a - c = b) + +只有整数可以进行位运算 + + + +## 1.3 数学函数库 + +![截屏2021-03-02 上午10.03.31](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-03-02%20%E4%B8%8A%E5%8D%8810.03.31.png) + + + +> 求一个数的立方根? + +```cpp +#include +#include +using namespace std; + +int main () { + + cout << pow(27, 1.0/3.0) << endl; + cout << pow(27, 1/3) << endl; + cout << pow(27, 1.0/3) << endl; + + return 0; +} +``` + +``` +3 +1 +3 +``` + + + +1/3向下取整会变成0所以`pow(27, 1/3)=1` + + + +![截屏2021-03-02 上午10.11.56](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-03-02%20%E4%B8%8A%E5%8D%8810.11.56.png) + +![截屏2021-03-02 上午10.12.06](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-03-02%20%E4%B8%8A%E5%8D%8810.12.06.png) + + + + + +## 1.4 程序流程控制方法 + +关系运算符逻辑返回值为true、false + +C语言支持bool类型(c99以上),头文件`` + +```cpp +代表false值:0、NULL、"\0"("\0" = 0) +true:非0记为真,非空即为真 +!!(x):逻辑归一化:将 + +cout << !!(-9) << endl; +1 + +``` + + + +### 分支结构 + +顺序 + +选择(分支) + +循环 + + + +switch(a) + +a必须能映射成一个整数值(字符和整数) + + + + + +## day1作业 + +haizeioj 131 132 134 135 142 143 + + + +## 1.5循环结构 + + + +```cpp +while () { + +} +do { + +} while(); + +for (初始化;循环条件;执行后操作) { + +} +``` + + + + + +```cpp +#include +using namespace std; + +int reverse_num(int n) {//回文判断 + int sum = 0; + int x = n; + while (x) { + sum = sum * 10 + x % 10; + x /= 10; + } + return sum == n; +} + +int main() { + + + + //短路原则控制输出格式 + for (int i = 10; i >= 0; i--) { + printf("%d", i); + if (i != 0) printf(" "); + } + printf("\n"); + for (int i = 10; i >= 0; i--) { + printf("%d", i); + i && printf(" "); + } + printf("\n"); + + int a = 0; + int b = 0; + + + //短路原则 + if ((a++) && (b++)) { + cout << "a = " << a << ", b = " << b << endl; + printf("true\n"); + } else { + cout << "a = " << a << ", b = " << b << endl; + printf("false\n"); + } + + cout << "a = " << a << ", b = " << b << endl; + + if ((b++) || (a++)) { + printf("true\n"); + } else { + printf("false\n"); + } + printf("a = %d, b = %d\n", a, b); + //回文判断 + int n; + while (~scanf("%d", &n)) { + cout << "reverse_num1 : " << endl; + if (reverse_num(n)) cout << "Yes" << endl; + else cout << "No" << endl; + + cout << "reverse_num2 : " << endl; + cout << (reverse_num(n) ? "Yes" : "No") << endl; + } + + return 0; +} + +``` + + + +```cpp +10 9 8 7 6 5 4 3 2 1 0 +10 9 8 7 6 5 4 3 2 1 0 +a = 1, b = 0 +false +a = 1, b = 0 +true +a = 2, b = 1 +123 +reverse_num1 : +No +reverse_num2 : +No +123321 +reverse_num1 : +Yes +reverse_num2 : +Yes +``` + + + +## 1.6 函数 + + + +> 区分函数声名和函数定义? +> +> 函数声名:函数的名字, +> +> 报错:函数未声明:编译期(属于语法错误) +> +> 函数定义:函数的实现 +> +> 报错:函数未定义:链接期,找不到函数具体实现的方式 + + + +源文件—>(预编译期进行宏替换)—>编译文件—>(编译期,检查语法错误)—>链接期(生成可执行的二进制文件) + + + +## 1.7 递归 + +递归:程序调用自身的编程技巧 + +递归是编程技巧 + +递归程序的组成部分 + +1. 边界条件处理 +2. 针对于问题的处理过程和递归过程 +3. 结果返回 + + + +执行过程:向下递推和向上回溯 + + + +所有的函数调用都需要借助栈的结构 + + + +## 1.8 函数指针 + +函数指针变量:接收存储不同类型的值,存储函数地址 + +用于传函数 + +数组是展开的函数 + +函数是压缩的数组 + + + +## 欧几里得算法 + +又名辗转相除法 + +用于计算最大公约数 + +gcd(a, b) <==> gcd(b, a % b) + +快速求解ax+by=1的一组整数解 + + + +## 变参函数 + +![截屏2021-03-03 下午4.15.37](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-03-03%20%E4%B8%8B%E5%8D%884.15.37.png) + +## day2作业 + + + +147 148 149 150 152 155 + + + + + +## 1.9数组与预处理命令 + +![截屏2021-03-05 上午8.33.39](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-03-05%20%E4%B8%8A%E5%8D%888.33.39.png) + + + + + +> sizeof() 是C语言的关键字,整个数组的容量 +> +> strlen() 是函数,获取字符串长度,获取当前数组中字符串的长度 + + + +malloc()动态申请空间,空间不一定是连续的,逻辑上一定是连续的 + +int arr[100] 静态申请,空间一定是连续的 + +最大申请的空间是200万个存储单元,8Mb + + + +```cpp +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +using namespace std; + +#define MAX_N 100 + +void func(int *p) {// p 一个指针变量,指向整型,为什么数组可以指向地址,指向数组收地址,加上地址偏移量,就可以访问整个数组 + printf("func : \n"); + return ; +} + +void func2(int p[MAX_N + 5][200]) {// 二维数组传参 + printf("func2 : \n"); +} +void func3(int p[][200]) {// 二维数组传参 + printf("func2 : \n"); +} +void func4(int (*p)[200]) {// 二维数组传参 + printf("func2 : \n"); +} + +int main() { + int arr[MAX_N + 5] = {0}; + int arr1[MAX_N + 5][200] = {0}; + for (int i = 0; i < 5; i++) { + //scanf("%d", &arr[i]); + scanf("%d", arr + i);// arr数组 + } + func(arr); + func2(arr1); + func3(arr1); + func4(arr1); + return 0; +} +``` + + + + + +## 素数 + + + + + +时间复杂度$O(N*\sqrt N)$ + + + + + +```cpp +#include +using namespace std; + +int is_prime(int n) { + for (int i = 2; i * i <= n; i++) { + if (n % i == 0) return 0; + } + return 1; +} + + +int main() { + int n; + cin >> n; + for (int i = 2; i <= n; i++) { + if (!is_prime(i)) continue; + cout << i << endl; + } + + return 0; +} +``` + + + +## 素数筛 + +时间复杂度$O(N \times loglogN)$趋近于$O(N)$ + +> 思想:用素数标记掉不是素数的数字,例如我知道了i是素数,那么`2*i`,`3*i`...不是素数 + + + +```cpp +#include +#include +using namespace std; + +#define MAX_N 100000 + +int prime[MAX_N + 5] = {0}; +void init() { // 素数筛:标记合数 + for (int i = 2; i <= MAX_N; i++) { + if (prime[i]) continue; // 是合数 + prime[++prime[0]] = i; + for (int j = i * i; j <= MAX_N; j+= i) { // i不能超过10万 + prime[j] = 1; // 合数标记为1 + } + } +} + +void init1() { // 素数筛:标记合数 + for (int i = 2; i <= MAX_N; i++) { + if (!prime[i]) prime[++prime[0]] = i; // 是素数 + for (int j = i; j <= MAX_N / i; j++) { + prime[i * j] = 1; // 合数标记为1 + } + } +} + +int main() { + + int n; + init1(); + //cin >> n; + for (int i = 2, j = 1; i <= MAX_N; i++) { + cout << "prime[i] = " << prime[j++]; + } + return 0; +} + + +``` + + + + + +## 线性筛 + +时间复杂度$O(n)$ + + + +```cpp +#include +#include +using namespace std; + +#define MAX_N 100000 + + +void init2() { // 素数筛:标记合数 + for (int i = 2; i <= MAX_N; i++) { + if (!prime[i]) prime[++prime[0]] = i; // 是素数, 存到数组 + for (int j = 1; j <= prime[0]; j++) { + if (prime[j] * i > MAX_N) break; + prime[prime[j] * i] = 1; // 合数标记为1 + if (i % prime[j] == 0) break; + } + } + return ; +} + +int main() { + + int n; + init1(); + //cin >> n; + for (int i = 2, j = 1; i <= MAX_N; i++) { + if (!is_prime(i)) continue; + //cout << "prime[i] = " << prime[j++]; + cout << "prime[i] = " << prime[j++]; + cout << " , i = " << i << endl; + } + + + + return 0; +} + +``` + + + + + +## 折半查找 + +时间复杂度$O(logN)$ + + + +![截屏2021-03-10 下午6.43.55](https://guziqiu-pictures.oss-cn-hangzhou.aliyuncs.com/img/%E6%88%AA%E5%B1%8F2021-03-10%20%E4%B8%8B%E5%8D%886.43.55.png) + + + + + +```cpp +#include +#include +#include +using namespace std; + +#define MAX_N 100 + +int binary_search(int *arr, int n, int x) { // 二分查找 + int head = 0, tail = n - 1, mid; + while (head <= tail) { + mid = (head + tail) >> 1; + if (arr[mid] == x) return 1; + if (arr[mid] < x) head = mid + 1; + else tail = mid - 1; + } + return 0; +} + + + +int main() { + int n; + cin >> n; + + for (int i = 0; i < n; i++) { + cin >> arr[i]; + } + + int x; + + while (~scanf("%d", &x)) { + printf("%s\n", binary_search(arr, n, x) ? "YES" : "NO"); + } + return 0; +} +``` + + + + + + + +```cpp +#include +#include +#include +using namespace std; +// 二分查找实现开平方根 +#define EPSL 1e-8 +double binary_search(double n) { + double head = 0, tail = n, mid; + if (n < 1.0) tail = 1.0; // 解决 0 < n < 1 的问题 + while (tail - head > EPSL) { + mid = (head + tail) / 2.0; + //if (fabs(mid * mid - n < EPSL)) return mid; + if (mid * mid < n) head = mid; + else tail = mid; + } + return head; +} + +int main() { + double n; + cin >> n; + cout << "binary_search_sqrt:" << binary_search(n) << endl; + cout << "srqt:" << sqrt(n) << endl; + return 0; + +} +``` + + + + + + + +```cpp +#include +#include +using namespace std; + +// 00000000011111111 找第一个1 +int binary_search1(int *arr, int n, int x) { + int head = 0, tail = n, mid; + while (head < tail) { + mid = (head + tail) >> 1; + if (arr[mid] == x) tail = mid; + else head = mid + 1; + } + return head == n ? -1 : head; +} + +// 11111111111110000000 找最后一个1 +int binary_search2(int *arr, int n, int x) { + int head = -1, tail = n - 1, mid; // 虚拟头指针 -1 + while (head < tail) { + mid = (head + tail + 1) >> 1; + if (arr[mid] == x) head = mid; + else tail = mid - 1; + } + return head; +} + +int main() { + //int arr1[10] = {1, 1, 1, 1, 1, 1, 1, 1, 1, 1}; + int arr1[10] = {0}; + int arr2[10] = {1, 1, 1, 1, 1, 1, 1, 1, 1, 1}; + int arr3[10] = {1, 1, 1, 1, 1, 1, 1, 1, 1, 0}; + printf("%d\n", binary_search2(arr1, 10, 1)); + + return 0; +} +``` + + + + + +## day3 + +168-174 + + + + + + + + + + + + + + + + + + + + + + + + + + + +## 00.end + + + + + + + +# 2.基础数据结构 + + + +## 1.顺序表代码演示 + + + + + +> malloc直接申请内存,内存不一定为空 +> +> calloc申请的内存一定为空, +> +> realloc在原空间进行重新开辟 + + + + + +```cpp +#include +#include +#include +using namespace std; + + +#define COLOR(a, b) "\033[" #b "m" a "\033[0m" +#define GREEN(a) COLOR(a, 32) + + +typedef struct Vector { + int *data; + int size, length; +} vec; + +vec *init(int n) { + vec *v = (vec *)malloc(sizeof(vec)); // malloc申请的是内存上的堆区,不在栈区 + v->data = (int *)malloc(sizeof(int) * n); + v->size = n; + v->length = 0; + return v; +} + +int expand(vec *v) { + int extr_size = v->size; + int *p; + while (extr_size) { + p = (int *)realloc(v->data, sizeof(int) * (v->size + extr_size)); + if (p) break; + extr_size >>= 1; + } + if (p == NULL) return 0; + v->data = p; + v->size += extr_size; + return 1; +} + +int insert(vec *v, int ind, int val) { + if (v == NULL) return 0; + if (ind < 0 || ind > v->length) return 0; + if (v->length == v->size) { + if (!expand(v)) return 0; + cout << GREEN("success to expand ! the size = ") << v->size << endl; + } + for (int i = v->length; i > ind; i--) { + v->data[i] = v->data[i - 1]; + } + v->data[ind] = val; + v->length += 1; + return 1; +} + +int erase(vec *v, int ind) { + if (v == NULL) return 0; + if (ind < 0 || ind >= v->length) return 0; + for (int i = ind; i < v->length; i++) { + v->data[i] = v->data[i + 1]; + } + v->length -= 1; + return 1; +} + +void output(vec *v) { + if (v == NULL) return ; + printf("vec : [ "); + for (int i = 0; i < v->length; i++) { + i && cout << ", "; + cout << v->data[i]; + } + cout << "]" << endl; +} + +void clear(vec *v) { + if (v == NULL) return ; + free(v->data); + free(v); + return ; +} + + + +int main() { + #define MAX_N 20 + vec *v = init(4); + srand(time(0)); + + for (int i = 0; i < MAX_N; i++) { + int op = rand() % 4; + int val = rand() % 100; + int ind = rand() % (v->length + 3) - 1; // [-1, length + 2] + switch (op) { + case 0: + case 1: + case 2: + printf("insert %d at %d to vector = %d\n", val, ind, insert(v, ind, val)); + break; + case 3: + printf("erase a iterm at %d from vector = %d\n", ind, erase(v, ind)); + break; + + } + output(v); + cout << endl; + } + #undef MAX_N + clear(v); + + return 0; +} +``` + + + + + + + + + +基本了解和使用C++面向对象,可以进行简单的开发和应用 + +技术能力 +熟悉使用linux,可以在linux进行系统编程 + +熟悉了解计算机网络操作系统等知识 + +了解基本的算法和数据结构 + + + + + +## 2.链表代码演示 + + + +> 链表 适合频繁插入删除$O(1)$,不需要扩容 +> +> 顺序表 适合查询$O(N)$,有容量大小,需要扩容 + + + +> 内存泄漏 +> +> 由于代码的错误,无妨访问一部分空间,但是操作系统认为这部分空间是我们的 + + + + + +```cpp +#include +#include +#include +#include +using namespace std; + +typedef struct Node { + int data; + struct Node *next; +} Node; + +typedef struct List { + Node head; // 虚拟头节点 + int length; +} List; + + +Node *getNewNode(int); +List *init_list(); +void clear_node(Node *); +int clear(List *); +int insert(List *, int, int); +int erase(List *, int); +void output(List *); +void reverse(List *); // 原地翻转 + + +int main() { + + srand(time(0)); +#define MAX_OP 20 + List *l = init_list(); + for (int i = 0; i < MAX_OP; i++) { + int val = rand() % 100; + int ind = rand() % (l->length + 3) - 1; + int op = rand() % 5; + switch (op) { + case 0: + case 1: + case 2: + printf("insert %d at %d to list = %d \n", val, ind, insert(l, ind, val)); + break; + case 3: + printf("erase a iterm at %d from list = %d\n", ind, erase(l, ind)); + break; + case 4: + printf("reverse the list \n"), reverse(l); + //output(l); + break; + } + output(l), cout << endl; + } + #undef MAX_OP + clear(l); + + + return 0; +} + +void output(List *l) { + printf("List(%d) : ", l->length); + for (Node *p = l->head.next; p != NULL; p = p->next) { + printf("%d->", p->data); + } + printf("NULL\n"); + return ; +} + +Node *getNewNode(int val) { + Node *p = (Node *)malloc(sizeof(Node)); + p->data = val; + p->next = NULL; + return p; +} + +List *init_list() { + List *list = (List *)malloc(sizeof(List)); + list->head.next = NULL; + list->length = 0; + return list; +} + + +void clear_node(Node *node) { + if (node == NULL) return ; + free(node); + return ; +} + +int clear(List *list) { + if (list == NULL) return 0; + Node *p = list->head.next, *q; + while (p) { + q = p->next; + clear_node(p); + p = q; + } + free(list); + return 1; +} + + +int insert(List *list, int ind, int val) { + if (list == NULL) return 0; + if (ind < 0 || ind > list->length) return 0; + + Node *p = &(list->head); + Node *node = getNewNode(val); + + while (ind--) p = p->next; + node->next = p->next; + p->next = node; + list->length += 1; + return 1; +} + +int erase(List *list, int ind) { + if (list == NULL) return 0; + if (ind < 0 || ind >= list->length) return 0; + Node *p = &(list->head); + Node *q; + while (ind--) p = p->next; + q = p->next; + p->next = q->next; + clear_node(q); + list->length -= 1; + return 1; +} + +void reverse(List *l) { + if (l == NULL) return ; + Node *p = l->head.next; + Node *q; + l->head.next = NULL;; + while (p) { + q = p->next; + p->next = l->head.next; + l->head.next = p; + p = q; + } + return ; +} +``` + + + +## day7 450-456 + + + + + + + +## 3.栈和队列 + +队列先进先出FIFO + + + + + +## 4.队列代码演示 + +```cpp +#include +#include +#include +#include +using namespace std; + + +#define COLOR(a, b) "\033[" #b "m" a "\033[0m" +#define GREEN(a) COLOR(a, 32) + +typedef struct Queue { + int *data; // 首地址 + int head, tail, size, count; +} queue; + +queue *init(int n) { + queue *q = (queue *)malloc(sizeof(queue)); + q->data = (int *)malloc(sizeof(int) * n); + q->size = n; + q->head = q->tail = 0; + q->count = 0; + return q; +} + +int empty(queue *q) { + if (q == NULL) return -1; + return q->count == 0; +} + +int front(queue *q) { + return q->data[q->head]; +} + +int expand(queue *q) { + int extr_size = q->size; + int *p; + while (extr_size) { + p = (int *)malloc(sizeof(int) * (q->size + extr_size)); + if (p != NULL) break; + extr_size >>= 1; + } + if (p == NULL) return 0; + for (int i = q->head, j = 0; j < q->count; j++) { + p[j] = q->data[(i + j) % q->size]; + } + free(q->data); + q->data = p; + q->head = 0; + q->size += extr_size; + q->tail = q->count; + return 1; +} + +int push(queue *q, int val) { + if (q == NULL) return 0; + if (q->count == q->size) { + if (!expand(q)) return 0; + printf(GREEN("success to expand ! the size = %d \n"), q->size); + } + q->data[q->tail++] = val; + if (q->tail == q->size) q->tail = 0; + q->count++; + return 1; +} + +int pop(queue *q) { + if (q == NULL) return -1; + if (empty(q)) return 0; + q->head += 1; + if (q->head == q->size) q->head = 0; + q->count--; + return 1; +} + +void output(queue *q) { + cout << "queue : ["; + for (int i = q->head, j = 0; j < q->count; j++) { + j && cout << " "; + cout << q->data[(i + j) % q->size]; + } + cout << "]" << endl; + return ; +} + +void clear(queue *q) { + if (q == NULL) return ; + free(q->data); + free(q); + return ; +} + +int main() { + #define MAX_OP 20 + queue *q = init(3); + srand(time(0)); + for (int i = 0; i < MAX_OP; i++) { + int op = rand() % 4; + int val = rand() % 100; + switch (op) { + case 0: + case 1: + case 2: + printf("push %d to queue = %d\n", val, push(q, val)); + break; + case 3: + if (!empty(q)) printf("success to pop!\n pop %d from the queue \n", front(q)), pop(q); + else printf("fail to pop ! the queue is empty!\n"); + break; + } + output(q), cout << endl;; + } + #undef MAX_OP + clear(q); + + return 0; +} +``` + + + + + + + + + +realloc()在原有的基础上进行扩容,扩容到原来的二倍,如果内存不够,就会拷贝到新的空间,把内容复制过来,释放原来的内存,新的空间是原来的二倍。如果扩容失败,会返回null + + + + + +## 5.栈代码演示 + +FILO + + + + + +> 栈顶指针top,初始化的时候是-1 + + + +```cpp +#include +#include +#include +#include +using namespace std; + +typedef struct stack { + int *data; + int top, size; +} stack; + +stack *init(int n) { + stack *s = (stack *)malloc(sizeof(stack)); + s->data = (int *)malloc(sizeof(int) * n); + s->size = n; + s->top = -1; // 栈顶指针 + return s; +} + +int empty(stack *s) { + return s->top == -1; +} + +int top(stack *s) { + return s->data[s->top]; +} + +int push(stack *s, int val) { + if (s == NULL) return 0; + if (s->top == s->size - 1) return 0; + s->data[++s->top] = val; + return 1; +} + +int pop(stack *s) { + if (s == NULL) return 0; + if (empty(s)) return 0; + s->top--; + return 1; +} + +void clear(stack *s) { + if (s == NULL) return ; + free(s->data); + free(s); + return ; +} + +void output(stack *s) { + cout << "stack : ["; + for (int i = 0; i <= s->top; i++) { + i && cout << " ,"; + cout << s->data[i]; + } + cout << "]"; +} + +int main() { + #define MAX_OP 20 + stack *s = init(MAX_OP); + srand(time(0)); + + for (int i = 0; i < MAX_OP; i++) { + int op = rand() % 4; + int val = rand() % 100; + switch (op) { + case 0: + case 1: + case 2: + printf("push %d to stack = %d\n", val, push(s, val)); + break; + case 3: + if (!empty(s)) printf("seccess to pop! pop %d from stack!\n", top(s)),pop(s); + break; + } + output(s), printf("\n"); + } + #undef MAX_OP + clear(s); + return 0; +} +``` + + + +> 系统栈满了,简称爆栈 +> +> linux 8M +> +> win 2M + + + +## day8 263 - 266 + + + +## 6.排序和查找 + + + +> 排序和查找分为哪些 + +第一类是稳定排序和第二类是非稳定排序 + +还有内部排序和外部排序 + +稳定排序:插入、冒泡、归并 + +> 判断稳定排序和非稳定排序 + +对相同值元素进行排序(a[i] == a[j])a[i]在z[j]前面,a[i]a[j]相对位置没有改变==>稳定排序 + + + +## 插入排序 + +时间复杂度$O(N^2)$ + + + +## 冒泡排序 + + + + + +## 归并排序 + +$O(N \log N)$ + + + +排序的内容比较多时采用归并排序 + + + +## 稳定排序代码演示 + + + +```cpp +#include +#include +#include +#include +using namespace std; + +#define swap(a, b) {\ + a ^= b; b^= a; a ^= b;\ +} + +#define TEST(arr, n, func, args...) {\ + int *num = (int *)malloc(sizeof(int) * n);\ + memcpy(num, arr, sizeof(int) * n);\ + output(num, n);\ + printf("%s : \n", #func);\ + func(args);\ + output(num, n);\ + free(num);\ +} + +//插入排序 +void insert_sort(int *num, int n) { + for (int i = 1; i < n; i++) { + for (int j = i; j > 0 && num[j] < num[j - 1]; j--) { + swap(num[j], num[j - 1]); + } + } + return ; +} + +//冒泡排序 +void bubble_sort(int *num, int n) { + int times; + for (int i = 1; i < n && times; i++) {//times,如果有一轮排序没有发生互换位置,则终止循环 + times = 0; + for (int j = 0; j < n - 1; j++) { + if (num[j] <= num[j+ 1]) continue; + swap(num[j], num[j + 1]); + times++; + //if (num[j] > num[j + 1]) { + // swap(num[j], num[j + 1]); + // times++; + //} + } + } +} + +//归并排序 +void merage_sort(int *num, int l, int r) { + if (r - l <= 1) { + if (r - l == 1 && num[r] < num[l]) { + swap(num[r], num[l]); + } + return ; + } + //数组分为两段 + int mid = (l + r) >> 1; + merage_sort(num, l, mid); + merage_sort(num, mid + 1, r); + int *temp = (int *)malloc(sizeof(int) * (r - l + 1)); + //将两个有序的数组合并为一个 + int p1 = l, p2 = mid + 1, k = 0; + while (p1 <= mid || p2 <= r) {//当数组中有元素时进行合并 + if (p2 > r || (p1 <= mid && num[p1] < num[p2])) { + //p2 > r : 第二个数组已经没有元素了,p1 <= mid,第一个元素还有元素, + temp[k++] = num[p1++];//将两个数组中较小的那个值,赋值给temp,temp成升序排列 + } else { + temp[k++] = num[p2++]; + } + } + memcpy(num + l, temp, sizeof(int) * (r - l + 1));//将temp拷贝到num中 + free(temp); + return ; +} + +void randint(int *num, int n) {//随机生成100以内的数字 + while (n--) num[n] = rand() % 100; + return ; +} + +void output(int *num, int n) { + printf("["); + for (int i = 0; i < n; i++) { + printf("%d ", num[i]); + } + printf("]\n"); + return ; +} + +int main() { + //随机生成数组 + srand(time(0)); + #define max_n 20 + int arr[max_n]; + randint(arr, max_n); + TEST(arr, max_n, insert_sort, num, max_n); + TEST(arr, max_n, bubble_sort, num, max_n); + TEST(arr, max_n, merage_sort, num, 0, max_n - 1); + + #undef max_n + + + + return 0; +} + + + + +``` + + + + + +## 非稳定排序代码演示 + +```cpp +#include +#include +#include +#include +#include +using namespace std; + +#define swap(a, b) {\ + __typeof(a) __temp = a;\ + a = b; b = __temp;\ +} + +#define TEST(arr, n, func, args...) {\ + int *num = (int *)malloc(sizeof(int) * n);\ + memcpy(num, arr, sizeof(int) * n);\ + output(num, n);\ + printf("%s = ", #func);\ + func(args);\ + output(num, n);\ + free(num);\ +} + + +void select_sort(int *num, int n) { // 选择排序 + for (int i = 0; i < n - 1; i++) { + int ind = i; //待排序去下标 + for (int j = i + 1; j < n; j++) { + if (num[ind] > num[j]) ind = j; + } + swap(num[i], num[ind]); + } + return ; +} + +void quick_sort(int *num, int l, int r) { // 快排 + if (l > r) return ; + int x = l, y = r, z = num[x]; + while (x < y) { + while (x < y && num[y] > z) y--; + if (x < y) num[x++] = num[y]; + while (x < y && num[x] < z) x++; + if (x < y) num[y--] = num[x]; + } + num[x] = z; + quick_sort(num, l, x - 1); + quick_sort(num, x + 1, r); + return ; +} + + + + + + + + +void randint(int *num, int n) { // 生成n个100以内的随机数 + while (n--) num[n] = rand() % 100; + return ; +} + +void output(int *num, int n) { + cout << "["; + for (int i = 0; i < n; i++) { + cout << num[i] << " "; + } + cout << "]" << endl; + return ; +} + +int main() { + srand(time(0)); + #define MAX_N 10 + int arr[MAX_N]; + randint(arr, MAX_N); + + TEST(arr, MAX_N, select_sort, num, MAX_N); + TEST(arr, MAX_N, quick_sort, num, 0, MAX_N - 1); + + + #undef MAX_N + return 0; +} + + +``` + + + + + +## day9 + +## 7.树与二叉树 + + + +树的高度/深度:最高的结点的深度,树的高度和深度相等 + +深度:从根节点往下看,高度:从下往上(根节点)看 + +出度:出去的边数 + +叶子节点:度为0的节点 + + + +二叉树 + +节点个数=边数+1 + + + +## 8.堆与优先队列 + + + +堆的本质是完全二叉树 + + + +堆排序时间复杂度$$O(NlogN)$$ + +```cpp +#include +#include +#include +#include +#include + +using namespace std; + +#define swap(a, b) {\ + __typeof(a) __temp = a;\ + a = b, b = __temp;\ +} + +void downUpdate(int *arr, int n, int ind) { + while ((ind << 1) <= n) {//堆中还有元素就可以进行调整 + int temp = ind, l = ind << 1, r = ind << 1 | 1; + if (arr[l] > arr[temp]) temp = l; + if (r <= n && arr[r] > arr[temp]) temp = r; + if (temp == ind) break; + swap(arr[ind], arr[temp]); + ind = temp; + } + return ; +} + +void heap_sort(int *arr, int n) { // 堆排序 线性建堆 + arr -= 1; + for (int i = n >> 1; i >= 1; i--) { + downUpdate(arr, n, i); + } + for (int i = n; i > 1; i--) { + swap(arr[i], arr[1]); + downUpdate(arr, i - 1, 1); // 向下维护 + } +} + +void output(int *arr, int n) { + cout << "["; + for (int i = 0; i < n; i++) { + i && cout << ", "; + cout << arr[i]; + } + cout << "]" << endl;; +} + + +int main() { + srand(time(0)); + #define MAX_N 20 + int *arr = (int *)malloc(sizeof(int) * (MAX_N)); + for (int i = 0; i < MAX_N; i++) { + arr[i] = rand() % 100; + } + output(arr, MAX_N); + heap_sort(arr, MAX_N); + output(arr, MAX_N); + free(arr); + #undef MAX_N + + return 0; +} +``` + + + + + +## 9.森林与并查集 + + + +```cpp + +``` + + + + + + + + + +## day10 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +## 0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +## 00 + + + + + + + +# 3.算法 + +## 1.vector + +## 2.stack + +STL六大组件 + +容器、算法、迭代器、仿函数、配接器(适配器)、分配器(Allocators) + + + +deque双端队列 + + + +> deque是一个容器 +> +> stack和queue是一个适配器 + + + +字符串string + +优先队列,默认大顶堆 + +pair对 + +map有序键值对容器 + +map底层通常实现为红黑树 + +pair 按照first排序 + +## map + +```cpp +#include + +#include + +using namespace std; + +int main() { + map m; + m.insert(make_pair('a', 90)); + m['b'] = 88; + + if (m.count('a') == 1) { + cout << m['a'] << endl; + }// 90 + + if (m.count('b') == 1) { + cout << m['b'] << endl; + } // 88 + + if (m.count('c') == 1) { + cout << m['c'] << endl; + } else { + cout << "NO" << endl; + }// NO + + if (m['a']) cout << m['a'] << endl;// 90 + + cout << m['c'] << endl; // 0 访问没有元素会输出0 + cout << m.count('c') << endl; // 没有的元素会自动添加,默认值0 + + return 0; +} + +``` + + + +```cpp +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +using namespace std; + +int main() { + map m; + m["abc"] = 123; + m["xyz"] = 12; + m["asd"] = 45; + cout << m.size() << endl; + + for (auto it = m.begin(); it != m.end(); it++) { // 通过迭代器访问map + cout << it->first << " " << it->second << endl; + } + + return 0; +} + +``` + + + +```cpp +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +using namespace std; + +struct node { + int num, cnt; + bool operator<(const node &b) const { // 默认重载小于号 + return this->num < b.num; + } +}; + + +int main() { + map m; + node t = (node){5, 6}; + m[t] = 4; + t.num = 99; + m[t] = 7; + t.num = 88; + m[t] = 77; + for (auto it = m.begin(); it != m.end(); it++) { + cout << it->first.num << " " << it->second << endl; + } + +} + +``` + + + + + +```cpp +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +using namespace std; + +struct node { + string str; + int num; +}; + +bool cmp(const node &a, const node &b) { + if (a.num == b.num) { + return a.str < b.str; + } + return a.num < b.num; +} + +node x[105]; +int cnt; + + +int main() { + int n; + map m; + cin >> n; + for (int i = 0; i < n; i++) { + string t; + cin >> t; + m[t]++; + } + for (auto it = m.begin(); it != m.end(); it++) { + //cout << it->first << " " << it->second << endl; + x[cnt].str = it->first; + x[cnt].num = it->second; + cnt++; + } + sort(x, x + cnt, cmp); + for (int i = 0; i < cnt; i++) { + cout << x[i].str << " " << x[i].num << endl; + } + return 0; +} + +``` + + + +## set + +只有键没有值 + +```cpp +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +using namespace std; + +int main() { + set s; + s.insert(5); + s.insert(6); + s.insert(0); + s.insert(-2); + + + if (s.count(6)) cout << "6 YES" << endl; + else cout << "6 NO" << endl;// + + + for (auto it = s.begin(); it != s.end(); it++) { + cout << *it << " "; + } + cout << endl; + + + + return 0; +} + +``` + + + + + + + +> map键值对 set集合 +> +> map和set有什么区别? +> +> 一个是键值对,一个只有键 +> +> 键都是唯一的 + + + + + +leetcode + +19, 24,142 + + + + + +234,20 844 , + + + + + +## 00 + + + + + + + + + +# 0.end + + + + + + + + + diff --git "a/02.c++\347\254\224\350\256\260/12.\351\253\230\345\271\266\345\217\221\346\234\215\345\212\241\345\231\250\345\274\200\345\217\221.md" "b/02.c++\347\254\224\350\256\260/12.\351\253\230\345\271\266\345\217\221\346\234\215\345\212\241\345\231\250\345\274\200\345\217\221.md" new file mode 100644 index 0000000..727e235 --- /dev/null +++ "b/02.c++\347\254\224\350\256\260/12.\351\253\230\345\271\266\345\217\221\346\234\215\345\212\241\345\231\250\345\274\200\345\217\221.md" @@ -0,0 +1,14 @@ + + +title: 高并发服务器开发 + +date: 2021-3-10 18:08:15 + +tags: 高并发服务器开发 + +categories: 高并发服务器开发 + + + +--- + diff --git "a/02.c++\347\254\224\350\256\260/13.C++\347\256\227\346\263\225\351\242\230\346\200\273\347\273\223.md" "b/02.c++\347\254\224\350\256\260/13.C++\347\256\227\346\263\225\351\242\230\346\200\273\347\273\223.md" new file mode 100644 index 0000000..94411eb --- /dev/null +++ "b/02.c++\347\254\224\350\256\260/13.C++\347\256\227\346\263\225\351\242\230\346\200\273\347\273\223.md" @@ -0,0 +1,1067 @@ +# 1.双指针 + +## 1.1三数求和leetcode15 + +([leetcode15. 三数之和](https://leetcode-cn.com/problems/3sum/)) + +[算法 | 双指针套路总结](https://zhuanlan.zhihu.com/p/95747836) + + + + + +# 2.树 + + + +#### [226. 翻转二叉树](https://leetcode-cn.com/problems/invert-binary-tree/) + + + +> 思路 +> +> 因为的链表,将左右指针调换之后,只需再调换左右孩子的子孩子的左右结点就实现了二叉树的翻转,所以可以用递归解决:调换左右孩子即可 + + + +#### [235. 二叉搜索树的最近公共祖先](https://leetcode-cn.com/problems/lowest-common-ancestor-of-a-binary-search-tree/) + + + +> + + + +```cpp +TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) { + int a = root->val, b = p->val, c = q->val; + + // 分为三种情况 + // 1. 在左侧,递归左节点 + // 2.在右侧,递归遍历右节点 + // 3.在两侧 就是当前节点 + + if (a < b && a < c) { // 都在根节点右侧,在右侧找 + return lowestCommonAncestor(root->right, p, q); + } else if (a > b && a > c) {//在左侧 + return lowestCommonAncestor(root->left, p, q); + } + return root; + } +``` + + + + + +# 3.排序和查找 + + + +#### [1. 两数之和](https://leetcode-cn.com/problems/two-sum/) + +```cpp +class Solution { +public: + vector twoSum(vector& nums, int target) { + vector v; + unordered_map m; // 哈希表建map + + for (int i = 0; i < nums.size(); i++) { + int t = target - nums[i]; + if (m.count(t) != 0) { //没有在哈希表中出现过 + v.push_back(m[t]); + v.push_back(i); + break; + } + m[nums[i]] = i; // 没有这个数字 + } + return v; + + } +}; +``` + + + +在进行迭代并将元素插入到表中的同时,我们还会回过头来检查表中是否已经存在当前元素所对应的目标元素。如果它存在,那我们已经找到了对应解,并立即将其返回。 + + + +> 因为两个和都在数组中,nums = [2,7,11,15], target = 9 +> +> 9 - 2 = 7, 9 - 7 = 2; +> +> 如果我们在9-2时没有录入map,而是在9-7的时候录入map +> +> 这样就不会㕛重复值出现 + + + + + +#### [3. 无重复字符的最长子串](https://leetcode-cn.com/problems/longest-substring-without-repeating-characters/) + + + + + +```cpp +class Solution { +public: + int lengthOfLongestSubstring(string s) { + // 动态双指针,滑动窗口 + int mark[130] = {0}, l = -1; // 标记数组,出现一个就+1 + int ans = 0; + for (int i = 0; i < s.size(); i++) { + mark[s[i]]++; // 右指针出现次数+1 + while (mark[s[i]] > 1) { // 如果出现次数>1,左指针右移,出现次数-1 + l++; + mark[s[l]]--; + } + ans = max(ans, i - l); //记录长度 + } + return ans; + } +}; +``` + + + + + + + +#### [21. 合并两个有序链表](https://leetcode-cn.com/problems/merge-two-sorted-lists/) + + + +```cpp +/** + * Definition for singly-linked list. + * struct ListNode { + * int val; + * ListNode *next; + * ListNode() : val(0), next(nullptr) {} + * ListNode(int x) : val(x), next(nullptr) {} + * ListNode(int x, ListNode *next) : val(x), next(next) {} + * }; + */ +class Solution { +public: + ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) { + + ListNode *ret = new ListNode(0); + ListNode *p = ret; + if (l1 == nullptr && l2 == nullptr) return nullptr; + + while (l1 != nullptr && l2 != nullptr) { + + if (l1->val >= l2->val) { + ret->next = l2; + l2 = l2->next; + } else if (l1->val < l2->val) { + ret->next = l1; + l1 = l1->next; + } + ret = ret->next; + } + if (l1 != nullptr) { + ret->next = l1; + } + if (l2 != nullptr) { + ret->next = l2; + } + + return p->next; + } +}; +``` + + + + + +#### [35. 搜索插入位置](https://leetcode-cn.com/problems/search-insert-position/) + + + +```cpp +class Solution { +public: + int searchInsert(vector& nums, int target) { + if (nums[nums.size() - 1] < target) return nums.size(); + int x = 0, y = nums.size(), mid; + while (x < y) { + mid = (x + y) >> 1; + if (nums[mid] >= target) y = mid; + else x = mid + 1; + } + return x; + + } +}; +``` + + + +> 二分查找特殊情况,找到第一个比他大的数字 + + + + + +#### [38. 外观数列](https://leetcode-cn.com/problems/count-and-say/) + + + + + +```cpp +class Solution { +public: + void work(string &s, int cnt, char c) { + s = s + (char)(cnt + '0') + c; + } + + void func(string &s1, string &s2) { + int cnt = 1; + for (int i = 1; i < s1.size(); i++) { + if (s1[i] == s1[i - 1]) { + cnt++; + } else { + work(s2, cnt, s1[i - 1]); + cnt = 1; + } + } + work(s2, cnt, s1[s1.size() - 1]); + } + + string countAndSay(int n) { + // 从前往后遍历 + string ans[35] = {"", "1"}; + for (int i = 2; i <= n; i++) { + func(ans[i - 1], ans[i]); + } + return ans[n]; + } +}; +``` + + + +#### [88. 合并两个有序数组](https://leetcode-cn.com/problems/merge-sorted-array/) + + + + + +```cpp +class Solution { +public: + void merge(vector& nums1, int m, vector& nums2, int n) { + + for (int i = nums1.size() - 1, t = nums2.size() - 1; t >= 0; i--, t--) { + nums1[i] = nums2[t]; + } + sort(nums1.begin(), nums1.end()); + } +}; +``` + + + +不增加存储空间,从后往前添加数字 + +```cpp +class Solution { +public: + void merge(vector& nums1, int m, vector& nums2, int n) { + // 从后面往前添加数字, + for (int i = n + m - 1; i >= 0; i--) { + if (m == 0 || n != 0 && nums2[n - 1] > nums1[m - 1]) { + nums1[i] = nums2[n - 1]; + n--; + } else { + nums1[i] = nums1[m - 1]; + m--; + } + } + } +}; +``` + + + + + +#### [217. 存在重复元素](https://leetcode-cn.com/problems/contains-duplicate/) + +```cpp +class Solution { +public: + bool containsDuplicate(vector& nums) { + unordered_set s; + for (int i = 0; i < nums.size(); i++) { + if (s.count(nums[i] == 1)) return true; + s.insert(nums[i]); + } + return false; + } + +}; +``` + + + + + +#### [219. 存在重复元素 II](https://leetcode-cn.com/problems/contains-duplicate-ii/) + +维护一个滑动窗口,窗口大小为k=3 + +```cpp +class Solution { +public: + bool containsNearbyDuplicate(vector& nums, int k) { + unordered_set s; + for (int i = 0, j = 0; i < nums.size(); i++) { + if (s.count(nums[i]) == 1) return true; + s.insert(nums[i]); + if (i - j == k) { + s.erase(nums[j]); + j++; + } + } + + return false; + } +}; +``` + + + +#### [278. 第一个错误的版本](https://leetcode-cn.com/problems/first-bad-version/) + + + + + +二分查找 + +```cpp +// The API isBadVersion is defined for you. +// bool isBadVersion(int version); + +class Solution { +public: + int firstBadVersion(int n) { + int l = 1, r = n, mid; + while (l != r) { + mid = ((long long)l + r) >> 1; + if (isBadVersion(mid)) r = mid; + else l = mid + 1; + } + return r; + } +}; +``` + + + + + +#### [349. 两个数组的交集](https://leetcode-cn.com/problems/intersection-of-two-arrays/) + +```cpp +class Solution { +public: + vector intersection(vector& nums1, vector& nums2) { + vector ret; + unordered_set s1, s2; + // s1装nums1,s2去重 + for (int i = 0; i < nums1.size(); i++) { + s1.insert(nums1[i]); + } + for (int i = 0; i < nums2.size(); i++) { + if (s1.count(nums2[i]) == 1 && s2.count(nums2[i]) == 0) { + // s1有,s2无 + ret.push_back(nums2[i]); + s2.insert(nums2[i]); + } + } + return ret; + } +}; +``` + + + + + +#### [350. 两个数组的交集 II](https://leetcode-cn.com/problems/intersection-of-two-arrays-ii/) + +难度简单465收藏分享切换为英文接收动态反馈 + +```cpp +class Solution { +public: + vector intersect(vector& nums1, vector& nums2) { + unordered_map m; + vector ans; + for (int i = 0; i < nums1.size(); i++) { + m[nums1[i]]++; + + } + for (int i = 0; i < nums2.size(); i++) { + if (m.count(nums2[i]) == 1 && m[nums2[i]] > 0) { + ans.push_back(nums2[i]); + m[nums2[i]]--; + } + } + return ans; + + } +}; +``` + + + + + +#### [374. 猜数字大小](https://leetcode-cn.com/problems/guess-number-higher-or-lower/) + +二分查找 + +```cpp +/** + * Forward declaration of guess API. + * @param num your guess + * @return -1 if num is lower than the guess number + * 1 if num is higher than the guess number + * otherwise return 0 + * int guess(int num); + */ + +class Solution { +public: + int guessNumber(int n) { + long long l, r = n; + while (l <= r) { + long long mid = (l + r) / 2; + int t = guess(mid); + if (t == 0) return mid; + if (t == -1) r = mid - 1; + else l = mid + 1; + } + return -1; + } +}; +``` + + + +#### [378. 有序矩阵中第 K 小的元素](https://leetcode-cn.com/problems/kth-smallest-element-in-a-sorted-matrix/) + +```cpp +class Solution { +public: + // 利用矩阵的单调性,从左下角开始扫描矩阵, + // 如果该值>k,往上走,找到 > v, int num) { + int t = 0;// 有t个数字<= num + int x = v.size() - 1, y = 0; + while (x >= 0 && y < v.size()) { + if (v[x][y] <= num) { + t += x + 1; + y++; + } else { + x--; + } + } + return t; + } + int kthSmallest(vector >& matrix, int k) { + int n = matrix.size(), l = matrix[0][0], r = matrix[n - 1][n - 1]; + while (l != r) { // 二分查找 + int mid = ((long long)l + r) / 2; + int s = func(matrix, mid); // 小于等于mid的数字 + if (s < k) l = mid + 1;// 数小了 + else r = mid; + } + return r; + } +}; +``` + + + +#### [8. 字符串转换整数 (atoi)](https://leetcode-cn.com/problems/string-to-integer-atoi/) + + + +```cpp +class Solution { +public: + int myAtoi(string s) { + long long ans = 0, now = 0; + int flag = 1; + //处理无用空格 + for (; now < s.size(); now++) { + if (s[now] != ' ') { + break; + } + } + if (s[now] == '+') { + now++; + } else if (s[now] == '-'){ + flag = -1; + now++; + } + for (; now < s.size(); now++) { + if (s[now] < '0' || s[now] > '9' || -ans < INT_MIN) break; + //不是数字或者int越界 + ans = ans * 10 + s[now] - '0'; + } + ans *= flag; + if (ans > INT_MAX) return INT_MAX; + if (ans < INT_MIN) return INT_MIN; + return ans; + } +}; +``` + + + +#### [13. 罗马数字转整数](https://leetcode-cn.com/problems/roman-to-integer/) + +```cpp +class Solution { +public: + int romanToInt(string s) { + // 直接遍历 + int ans = 0; + for (int i = 0; i < s.size(); i++) { + if (s[i] == 'I') { + if (s[i + 1] == 'V') { + ans += 4; + i++; + } else if (s[i + 1] == 'X') { + ans += 9; + i++; + } else { + ans += 1; + } + } else if (s[i] == 'X') { + if (s[i + 1] == 'L') { + ans += 40; + i++; + } else if (s[i + 1] == 'C') { + ans += 90; + i++; + } else { + ans += 10; + } + } else if (s[i] == 'C') { + if (s[i + 1] == 'D') { + ans += 400; + i++; + } else if (s[i + 1] == 'M') { + ans += 900; + i++; + } else { + ans += 100; + } + } else if (s[i] == 'V') { + ans += 5; + } else if (s[i] == 'L') { + ans += 50; + } else if (s[i] == 'D'){ + ans += 500; + } else { + ans += 1000; + } + } + return ans ; + } +}; +``` + +直接遍历 + + + +#### [14. 最长公共前缀](https://leetcode-cn.com/problems/longest-common-prefix/) + +难度简单1525收藏分享切换为英文接收动态反馈 + + + +```cpp +class Solution { +public: + string longestCommonPrefix(vector& strs) { + if (strs.size() == 0) return ""; + // 遍历所有字符串 + string ans = strs[0]; // 初始化字符串 + for (int i = 1; i < strs.size(); i++) { + string t; + for (int j = 0; j < strs[i].size() && j < ans.size(); j++) { + if (strs[i][j] == ans[j]) { + t += ans[j]; + } else { + break; + } + } + ans = t; + } + return ans; + } +}; +``` + +#### [36. 有效的数独](https://leetcode-cn.com/problems/valid-sudoku/) + + + +```cpp +class Solution { +public: + bool isValidSudoku(vector>& board) { + int marka[10][10] = {0}; // 存储行中数字出现的次数 + int markb[10][10] = {0}; // 列 + int markc[10][10] = {0}; // 格子 + // 遍历 + for (int i = 0; i < 9; i++) { + for (int j = 0; j < 9; j++) { + if (board[i][j] >= '1' && board[i][j] <= '9') { + marka[i][board[i][j] - '0']++; + markb[j][board[i][j] - '0']++; + int t = (i / 3) * 3 + j / 3; + markc[t][board[i][j] - '0']++; + if (marka[i][board[i][j] - '0'] > 1 || + markb[j][board[i][j] - '0'] > 1 || + markc[t][board[i][j] - '0'] > 1) + return false; + } + } + } + return true; + } +}; +``` + + + +#### [58. 最后一个单词的长度](https://leetcode-cn.com/problems/length-of-last-word/) + +```cpp +58. class Solution { +public: + bool isPalindrome(string s) { + int l = 0, r = s.size() - 1; + while (l < r) { + // 越过不是数字和字母的位置 + while (l < s.size() + && !(s[l] >= '0' && s[l] <= '9' || s[l] >= 'A' && s[l] <= 'Z' || s[l] >= 'a' && s[l] <= 'z')) l++; + + while (r >= 0 && + !(s[r] >= '0' && s[r] <= '9' || s[r] >= 'A' && s[r] <= 'Z' || s[r] >= 'a' && s[r] <= 'z')) r--; + + // 越界 + if ( l == s.size() || r < 0) return true; + // 转换成小写 + if (s[l] >= 'A' && s[l] <= 'Z') { + s[l] += 'a' - 'A'; + } + if (s[r] >= 'A' && s[r] <= 'Z') { + s[r] += 'a' - 'A'; + } + if (s[r] != s[l]) return false; + l++; + r--; + } + return true; + + } +}; +``` + + + +判断双指针是否一致 + + + + + +#### [165. 比较版本号](https://leetcode-cn.com/problems/compare-version-numbers/) + +利用func函数将每一段分割,比较每一段的值 + +```cpp +class Solution { +public: + int func(string &s, int &ind) { + int t = 0; + while (ind < s.size() && s[ind] >= '0' && s[ind] <= '9') { + t = t * 10 + s[ind] - '0'; + ind++; + } + return t; + } + int compareVersion(string version1, string version2) { + int ind1 = 0, ind2 = 0; + while (ind1 < version1.size() || ind2 < version2.size()) { + int num1 = func(version1, ind1); + int num2 = func(version2, ind2); + if (num1 < num2) return -1; + if (num1 > num2) return 1; + ind1++; + ind2++; + } + return 0; + } +}; +``` + + + +#### [205. 同构字符串](https://leetcode-cn.com/problems/isomorphic-strings/) + + + +```cpp +class Solution { +public: + bool isIsomorphic(string s, string t) { + int marka[150] = {0}; + int markb[150] = {0}; + for (int i = 0; i < s.size(); i++) { + if (marka[s[i]] == 0 && markb[t[i]] == 0) { + marka[s[i]] = t[i]; + markb[t[i]] = s[i]; + } else if (marka[s[i]] != t[i] || markb[t[i]] != s[i]) { + return false; + } + } + return true; + } +}; +``` + + + +#### [242. 有效的字母异位词](https://leetcode-cn.com/problems/valid-anagram/) + + + +```cpp +class Solution { +public: + bool isAnagram(string s, string t) { + int marks[26] = {0}, markt[26] = {0}; + for (int i = 0; i < s.size(); i++) { + marks[s[i] - 'a']++; + } + for (int i = 0; i < t.size(); i++) { + markt[t[i] - 'a']++; + } + for (int i = 0; i < 26; i++) { + if (marks[i] ! = markt[i]) return false; + } + return true; + } +}; +``` + + + +#### [290. 单词规律](https://leetcode-cn.com/problems/word-pattern/)[290. 单词规律](https://leetcode-cn.com/problems/word-pattern/) + + + +```cpp +class Solution { +public: + bool wordPattern(string pattern, string s) { + unordered_map m1; + unordered_map m2; + string temp; //记录每一个单词 + int j = 0; // 遍历pattern + s += ' ';//为最后一个字符增加一个空格,遇到一个空格分隔一个单词 + for (int i = 0; i < s.size(); i++) { // 遍历s + if (s[i] == ' ') { + if (j == pattern.size() || + (m1.count(pattern[j]) && m1[pattern[j]] != temp) || + (m2.count(temp) && m2[temp] != pattern[j])) + // 遍历到结尾 || 字符和字符串不匹配 || 字符串和字符不匹配 + return false; + m1[pattern[j]] = temp; + m2[temp] = pattern[j]; + j++; // 匹配下一个字母 + temp = ""; + } else { + temp += s[i]; // 将字符添加到临时字符串 + } + } + return j == pattern.size(); // s和pattern长度可能不相等 + } +}; +``` + + + +#### [344. 反转字符串](https://leetcode-cn.com/problems/reverse-string/) + +```cpp +class Solution { +public: + void reverseString(vector& s) { + // 1. reverse(s.begin(), s.end()); + for (int i = 0, j = s.size() - 1; i < j; j--, i++) { + // 2. swap(s[i], s[j]); + char t = s[i]; + s[i] = s[j]; + s[j] = t; + } + } +}; +``` + + + +#### [345. 反转字符串中的元音字母](https://leetcode-cn.com/problems/reverse-vowels-of-a-string/) + +```cpp +class Solution { +public: + int func(char c) { // 元音字母返回1 + switch (c) { + case 'a': + case 'e': + case 'i': + case 'o': + case 'u': + case 'A': + case 'E': + case 'I': + case 'O': + case 'U': + return 1; + } + return 0; + } + string reverseVowels(string s) { + for (int i = 0, j = s.size() - 1; i < j; i++, j--) { + while (i < j && !func(s[i])) { // 不是元音字母就继续往右走 + i++; + } + while (i < j && !func(s[j])) {// 不是元音字母就继续往左走 + j--; + } + swap(s[i], s[j]); + } + return s; + } +}; +``` + + + + + +#### [383. 赎金信](https://leetcode-cn.com/problems/ransom-note/) + +计数问题 + +```cpp +class Solution { +public: + bool canConstruct(string ransomNote, string magazine) { + int num[26] = {0}; + for (int i = 0; i < magazine.size(); i++) { + num[magazine[i] - 'a']++; + } + for (int i = 0; i < ransomNote.size(); i++) { + if (num[ransomNote[i] - 'a'] == 0) return false; + num[ransomNote[i] - 'a']--; + } + return true; + + } +}; +``` + + + +#### [128. 最长连续序列](https://leetcode-cn.com/problems/longest-consecutive-sequence/) + +```cpp +class Solution { +public: + int longestConsecutive(vector& nums) { + // O(N) + unordered_set s; + for (int i = 0; i < nums.size(); i++) { + s.insert(nums[i]); + } + int ans = 0; + int temp = 0; + for (auto it = s.begin(); it != s.end(); it++) { + int t = *it; + int next = t + 1; + if (s.count(t - 1) == 1) continue; + while (s.count((next)) == 1) next++; + ans = max(ans, next - t); + } + return ans; + + + /* O(NlogN) + if (nums.empty()) return 0; + sort(nums.begin(), nums.end()); + int ans = 0; + int temp = 0; + for (int i = 1; i < nums.size(); i++) { + if (nums[i - 1] + 1 == nums[i]) temp++; + else if (nums[i - 1] == nums[i]) continue; + else temp = 0; + ans = max(temp, ans); + } + return ans + 1; + */ + } +};// 0 1 1 2 +``` + + + + + + + + + + + + + + + + + +# 剑指offer + + + +### 链表(8道): + +#### [剑指 Offer 52. 两个链表的第一个公共节点](https://leetcode-cn.com/problems/liang-ge-lian-biao-de-di-yi-ge-gong-gong-jie-dian-lcof/) + + + +```cpp +/** + * Definition for singly-linked list. + * struct ListNode { + * int val; + * ListNode *next; + * ListNode(int x) : val(x), next(NULL) {} + * }; + */ +class Solution { +public: + ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) { + ListNode *p = headA; + ListNode *q = headB; + + while (p != q) { + p = (p != nullptr ? p->next : headB); + q = (q != nullptr ? q->next : headA); + } + return p; + + } +}; +``` + + + + + +#### [剑指 Offer 06. 从尾到头打印链表](https://leetcode-cn.com/problems/cong-wei-dao-tou-da-yin-lian-biao-lcof/) + +```cpp +/** + * Definition for singly-linked list. + * struct ListNode { + * int val; + * ListNode *next; + * ListNode(int x) : val(x), next(NULL) {} + * }; + */ +class Solution { +public: + vector reversePrint(ListNode* head) { + stack s; + vector v; + while (head != nullptr) { + s.push(head->val); + + head = head->next; + + } + + while (!s.empty()){ + v.push_back(s.top()); + s.pop(); + } + return v; + } +}; + + +``` + + + + + + + +# 动态规划 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git "a/02.c++\347\254\224\350\256\260/14.\351\235\242\347\273\217.md" "b/02.c++\347\254\224\350\256\260/14.\351\235\242\347\273\217.md" new file mode 100644 index 0000000..8c7a72a --- /dev/null +++ "b/02.c++\347\254\224\350\256\260/14.\351\235\242\347\273\217.md" @@ -0,0 +1,1687 @@ +# 1.C/C++ + +## 1.变量的内存布局模型 + +![img](https://uploadfiles.nowcoder.com/images/20210329/675098158_1617010890605/7AFBB1602613EC52B265D7A54AD27330) + +![img](https://uploadfiles.nowcoder.com/images/20210329/675098158_1617010890605/7AFBB1602613EC52B265D7A54AD27330) + +**从低地址到高地址,一个程序由代码段、数据段、** **BSS** **段组成。** + +1. **代码段:**存放程序**执行代码**的一块内存区域。只读,代码段的头部还会包含一些**只读的常数变量**。 + +2. **数据段data:**存放程序中**已初始化**的**全局变量**和**静态变量**的一块内存区域。 + +3. **BSS** 段:存放程序中**未初始化**的**全局变量**和**静态变量**的一块内存区域。 + +4. 可执行程序在运行时又会多出两个区域:**堆区**和**栈区。** + + **堆区:**动态申请内存用。堆从低地址向高地址增长。 + + **栈区:**存储**局部变量**、**函数参数值**。栈从高地址向低地址增长。是一块连续的空间。 + +5. 最后还有一个**文件映射区(共享区)**,位于堆和栈之间。存放共享内存。。 + +## 2.**野指针与内存泄露** + +### 有哪些情况下会产生**野指针** + +(1)**指针变量的值未被初始化**: + +声明一个指针的时候,没有显示的对其进行初始化,那么该指针所指向的地址空间是乱指一气的。如果指针声明在全局数据区,那么未初始化的指针缺省为空,如果指针声明在栈区,那么该指针会随意指向一个地址空间。 + +(2)**指针所指向的地址空间已经被free或delete**: + +在堆上malloc或者new出来的地址空间,如果已经free或delete,那么此时堆上的内存已经被释放,但是指向该内存的指针如果没有人为的修改过,那么**指针还会继续指向这段堆上已经被释放的内存**,这时还通过该指针去访问堆上的内存,就会造成不可预知的结果,给程序带来隐患。 + +(3)**指针操作超越了作用域**: + +### 何避免野指针呢? + +(1)初始化置NULL + +(2)申请内存后判空:malloc申请内存后需要判空,而在现行C++标准中,如C++11,使用new申请内存后不用判空,因为发生错误将抛出异常。 + +(3)指针释放后置NULL + +(4)使用智能指针。 + + + +## 3.什么是**内存泄露**? + +(1)new和malloc申请资源使用后,没有用delete和free释放; + +(2)子类继承父类时,父类析构函数不是虚函数 + +(3)Windows句柄资源使用后没有释放。 + +有以下几种避免方法: + +第一:良好的编码习惯,使用了内存分配的函数,一旦使用完毕,要记得使用其相应的函数释放掉。 + +第二:将分配的内存的指针以链表的形式自行管理,使用完毕之后从链表中删除,程序结束时可检查改链表。 + +第三:使用智能指针。 + +第四:一些常见的工具插件可以帮助检测内存泄露,如ccmalloc、Dmalloc、Leaky、Valgrind等等。 + +## 4.内存碎片通常分为**内部碎片**和**外部碎片**: + +(1)**内部碎片**是由于采用固定大小的内存分区,当一个进程不能完全使用分给它的固定内存区域时就产生了内部碎片,通常内部碎片难以完全避免; + +(2)**外部碎片**是由于某些未分配的连续内存区域太小,以至于不能满足任意进程的内存分配请求,从而不能被进程利用的内存区域。再比如堆内存的频繁申请释放,也容易产生外部碎片。 + + + +## 5.说说new和malloc的区别,各自底层实现原理 + +1. new是操作符,而malloc是库函数。 +2. new在调用的时候先分配内存,在调用构造函数,释放的时候用delete调用析构函数;而malloc没有构造函数和析构函数。 +3. malloc需要给定申请内存的大小,返回的指针一般需要强转;new会调用构造函数,不用指定内存的大小,返回指针不用强转。 +4. new可以被重载;malloc不行 +5. new分配内存更直接和安全。 +6. new发生错误抛出异常,malloc返回null + + + +## 6.封装、继承、多态 + +- 封装:隐藏对象的属性和实现细节,仅对外公开接口和对象进行交互,将数据和操作数据的方法进行有机结合。被封装的元素隐藏了它们的实现细节–可以调用一个函数但是不能够访问函数所执行的语句。 + + > 将成员方法和成员属性写在一个类里面,对外公开的是一些函数,但不会公开具体的实现细节,隐藏实现细节,使得代码模块化; + +- 继承就是新类从已有类那里得到已有的特性。 类的派生指的是从已有类产生新类的过程。原有的类成为基类或父类,产生的新类称为派生类或子类,子类继承基类后,可以创建子类对象来调用基类函数,变量等 + + > 子类继承父类的所有的属性和方法,子类创建对象后可以父类的部分属性和方法访问,扩展已存在的代码模块(类);它们的目的都是为了代码重用 + +- 可以简单概括为“一个接口,多种方法”,即用的是同一个接口,但是效果各不相同,多态有两种形式的多态,一种是静态多态,一种是动态多态,实现的目的是为了接口重用 + + + +静态多态动态多态 + +虚函数-》虚函数表 + + +## 7.const + +> const和define的区别 + +const用于定义常量,define用于定义宏,也可以用于定义常量, + +const运行与编译期,define运行于预处理期 + +const定义常量时需要带类型,define定义常量的时候不需要带类型。 + + + +```cpp +1. const int a; //指的是a是一个常量,不允许修改。 +2. const int *a; //a指针所指向的内存里的值不变,即(*a)不变 +3. int const *a; //同const int *a; +4. int *const a; //a指针所指向的内存地址不变,即a不变 +5. const int *const a; //都不变,即(*a)不变,a也不变 +``` + + + +## static + + + +## 引用与指针 + +## 智能指针 + +# STL标准模板库 + +基于模板实现 + +容器算法迭代器 + +容器、算法algorithm、迭代器iterator + +指向容器的指针 + +## string + +### 1.string 是一个类,char*是一个指针, + +string封装了char\*,管理这个字符串,是一个char*类型的容器 + +### 2.string封装了很多实用的成员方法 + +查找find,拷贝copy,删除delete,替换replace,插入insert + +### 3.不考虑内存释放和越界 + +string管理char*所分配的内存,每一次string复制,取值都是由string类负责维护,不用担心复制越界和取值越界 + +### 4.string和char*可以相互转换吗,如果能,怎么转换? + +string转char *通过string提供c_str(); + + + +string复制 + +string find,rfind,append,replace,insert,erase,substr,compare + +## vector + +动态数组 + +单口容器 + +尾部插入不需要移动元素,效率高 + +vector增长原理: + +插入新元素时,如果空间不足,申请更大的空间,原空间拷贝到新空间,释放旧空间,再把新元素插入新申请的空间realloc + + + + + + + +# 2.操作系统 + +内存空间 + +![img](https://uploadfiles.nowcoder.com/images/20210329/675098158_1617011293853/7AFBB1602613EC52B265D7A54AD27330) + + + +| 数据段 | 存放程序中**已初始化**的**全局变量**和**静态变量**的一块内存区域。 | | +| ---------- | ------------------------------------------------------------ | ---- | +| 代码段 | 存放程序**执行代码**的一块内存区域。只读,代码段的头部还会包含一些**只读的常数变量** | | +| **BSS** 段 | 全局/静态未初始化数据:存放程序中**未初始化**的**全局变量**和**静态变量**的一块内存区域。 | | +| **堆区** | 动态申请内存用。堆从低地址向高地址增长。 | | +| **栈区** | 存储**局部变量**、**函数参数值**。栈从高地址向低地址增长。是一块连续的空间。 | | + + + +代码段:存放指令,全局区存放变量,堆栈区,内核空间 + +## 1.进程 + +### 进程 + +我们写的代码只是一个存储在硬盘的静态文件,通过编译后就会生成二进制可执行文件,当我们运行这个可执行文件后,它会被装载到内存中,接着 CPU 会执行程序中的每一条指令,那么这个**运行中的程序,就被称为「进程」**。 + +进程:程序是指令、数据及其组织形式的描述,而**进程**则是程序的运行实例,包括程序计数器、寄存器和变量的当前值。 + + + +进程结构:一般分为**三部分**:**代码段、数据段和堆栈段**。也就是程序,数据,进程控制块PCB(进程控制块) + +**代码段**用于存放程序代码,如果有多个进程运行相同的一个程序,那么它们可以使用同一个代码段。 + +(因为要进程是程序运行的实例,是程序就会有代码,所以就有代码段) + +**数据段**则存放程序的全局变量、常量和静态变量。 + +(进程之间可能要切换,需要存储当前环境的值) + +**堆栈段**中的栈用于函数调用,存放着函数的参数、局部变量。PCB + +### 进程控制块PCB + +进程控制块的以下信息分为四类: + +- 进程的标识符 +- 进程状态 +- 进程调度信息 +- 进程控制信息 + + + +### PCB 具体包含什么信息呢? + +**进程描述信息:** + +- 进程标识符:标识各个进程,每个进程都有一个并且唯一的标识符; +- 用户标识符:进程归属的用户,用户标识符主要为共享和保护服务; + +**进程控制和管理信息:** + +- 进程当前状态,如 new、ready、running、waiting 或 blocked 等; +- 进程优先级:进程抢占 CPU 时的优先级; + +**资源分配清单:** + +- 有关内存地址空间或虚拟地址空间的信息,所打开文件的列表和所使用的 I/O 设备信息。 + +**CPU 相关信息:** + +- CPU 中各个寄存器的值,当进程被切换时,CPU 的状态信息都会被保存在相应的 PCB 中,以便进程重新执行时,能从断点处继续执行。 + +### 进程状态 + +**01 创建进程** + +操作系统允许一个进程创建另一个进程,而且允许子进程继承父进程所拥有的资源,当子进程被终止时,其在父进程处继承的资源应当还给父进程。同时,终止父进程时同时也会终止其所有的子进程。 + +创建进程的过程如下: + +- 为新进程分配一个唯一的进程标识号,并申请一个空白的 PCB,PCB 是有限的,若申请失败则创建失败; +- 为进程分配资源,此处如果资源不足,进程就会进入等待状态,以等待资源; +- 初始化 PCB; +- 如果进程的调度队列能够接纳新进程,那就将进程插入到就绪队列,等待被调度运行; + +**02 终止进程** + +进程可以有 3 种终止方式:正常结束、异常结束以及外界干预(信号 `kill` 掉)。 + +终止进程的过程如下: + +- 查找需要终止的进程的 PCB; +- 如果处于执行状态,则立即终止该进程的执行,然后将 CPU 资源分配给其他进程; +- 如果其还有子进程,则应将其所有子进程终止; +- 将该进程所拥有的全部资源都归还给父进程或操作系统; +- 将其从 PCB 所在队列中删除; + +**03 阻塞进程** + +当进程需要等待某一事件完成时,它可以调用阻塞语句把自己阻塞等待。而一旦被阻塞等待,它只能由另一个进程唤醒。 + +阻塞进程的过程如下: + +- 找到将要被阻塞进程标识号对应的 PCB; +- 如果该进程为运行状态,则保护其现场,将其状态转为阻塞状态,停止运行; +- 将该 PCB 插入的阻塞队列中去; + +**04 唤醒进程** + +进程由「运行」转变为「阻塞」状态是由于进程必须等待某一事件的完成,所以处于阻塞状态的进程是绝对不可能叫醒自己的。 + +如果某进程正在等待 I/O 事件,需由别的进程发消息给它,则只有当该进程所期待的事件出现时,才由发现者进程用唤醒语句叫醒它。 + +唤醒进程的过程如下: + +- 在该事件的阻塞队列中找到相应进程的 PCB; +- 将其从阻塞队列中移出,并置其状态为就绪状态; +- 把该 PCB 插入到就绪队列中,等待调度程序调度; + +进程的阻塞和唤醒是一对功能相反的语句,如果某个进程调用了阻塞语句,则必有一个与之对应的唤醒语句。 + + + +### 程序是怎么转换为进程的呢?进程的创建 + +1. 内核将程序读入内存,为程序分配内存空间。 +2. 内核为该进程分配进**程标识符**(**PID**,记住这个名称)和其他所需资源 +3. 内核为进程保存PID及相应的状态信息,并且将进程放入运行队列中等待执行。 +4. 由操作系统调度执行。 + + + + + + + +对进程的理解? + +**进程**是程序的运行实例,包括程序计数器、寄存器和变量的当前值。 + +进程结构一般分为**三部分**:**代码段、数据段和堆栈段**。 + +- **代码段**用于存放程序代码,如果有多个进程运行相同的一个程序,那么它们可以使用同一个代码段。 +- **数据段**则存放程序的全局变量、常量和静态变量。 +- **堆栈段**中的栈用于函数调用,存放着函数的参数、局部变量。 + + + + + + + +### 1.进程间通信方式 + +管道、IPC(消息队列、共享内存、信号量、信号)、套接字 + +管道: + +fork创建的父子进程之间不共享数据段和堆栈段,它们之间是通过**管道**进行通信的。**管道是一种两个进程间进行单向通信的机制**。因为这种单向性,管道又称为半双工管道,所以其使用是有一定的局限性的。半双工是指数据只能由一个进程流向另一个进程(一个管道负责读,一个管道负责写);如果是全双工通信,需要建立两个管道。**管道本质是一种文件** + +消息队列: + +**消息队列**是在一个系统内核中建立一个保存消息的队列,表现形式为消息链表。消息链表中节点的结构用msg声明。进程消息队列通信,需要两个进程,一个接受消息,一个发送消息, + +### 2.进程同步的方式? + +1. **信号量semaphore**:是一个计数器,可以用来控制多个进程对共享资源的访问。信号量用于实现进程间的互斥与同步。P操作(递减操作)可以用于阻塞一个进程,V操作(增加操作)可以用于解除阻塞一个进程。 +2. **管道**:一个进程通过调用管程的一个过程进入管程。在任何时候,只能有一个进程在管程中执行,调用管程的任何其他进程都被阻塞,以等待管程可用。 +3. **消息队列**:消息的链接表,放在内核中。消息队列独立于发送与接收进程,进程终止时,消息队列及其内容并不会被删除;消息队列可以实现消息的随机查询,可以按照消息的类型读取。 +4. **互斥锁** + + + +### 3.线程间通信 + +1. **互斥锁**。 +2. **信号量**。 +3. **条件变量**。 +4. **读写锁**。 + + + +### 4.**进程与线程的概念,以及为什么要有进程线程,其中有什么区别,他们各自又是怎么同步的** + +进程是对运行时程序的封装,是系统进行资源调度和分配的的基本单位,实现了操作系统的并发; + +线程是进程的子任务,是CPU调度和分派的基本单位,用于保证程序的实时性,实现进程内部的并发;线程是操作系统可识别的最小执行和调度单位。每个线程都独自占用一个虚拟处理器:独自的寄存器组,指令计数器和处理器状态。每个线程完成不同的任务,但是共享同一地址空间(也就是同样的动态内存,映射文件,目标代码等等),打开的文件队列和其他内核资源 + + + +区别: + +1.一个线程只能属于一个进程,而一个进程可以有多个线程,但至少有一个线程。线程依赖于进程而存在。 + +2.进程在执行过程中拥有独立的内存单元,而多个线程共享进程的内存。(资源分配给进程,同一进程的所有线程共享该进程的所有资源。同一进程中的多个线程共享代码段(代码和常量),数据段(全局变量和静态变量),扩展段(堆存储)。但是每个线程拥有自己的栈段,栈段又叫运行时段,用来存放所有局部变量和临时变量。) + +3.进程是资源分配的最小单位,线程是CPU调度的最小单位; + +4.系统开销: 由于在创建或撤消进程时,系统都要为之分配或回收资源,如内存空间、I/o设备等。因此,操作系统所付出的开销将显著地大于在创建或撤消线程时的开销。类似地,在进行进程切换时,涉及到整个当前进程CPU环境的保存以及新被调度运行的进程的CPU环境的设置。而线程切换只须保存和设置少量寄存器的内容,并不涉及存储器管理方面的操作。可见,进程切换的开销也远大于线程切换的开销。 + +5.通信:由于同一进程中的多个线程具有相同的地址空间,致使它们之间的同步和通信的实现,也变得比较容易。进程间通信IPC,线程间可以直接读写进程数据段(如全局变量)来进行通信——需要进程同步和互斥手段的辅助,以保证数据的一致性。在有的系统中,线程的切换、同步和通信都无须操作系统内核的干预 + +6.进程编程调试简单可靠性高,但是创建销毁开销大;线程正相反,开销小,切换速度快,但是编程调试相对复杂。 + +7.进程间不会相互影响 ;线程一个线程挂掉将导致整个进程挂掉 + +8.进程适应于多核、多机分布;线程适用于多核 + +进程间通信的方式: + +进程间通信主要包括管道、系统IPC(包括消息队列、信号量、信号、共享内存等)、以及套接字socket。 + +1.管道: + +管道主要包括无名管道和命名管道:管道可用于具有亲缘关系的父子进程间的通信,有名管道除了具有管道所具有的功能外,它还允许无亲缘关系进程间的通信 + +1.1 普通管道PIPE: + +1)它是半双工的(即数据只能在一个方向上流动),具有固定的读端和写端 + +2)它只能用于具有亲缘关系的进程之间的通信(也是父子进程或者兄弟进程之间) + +3)它可以看成是一种特殊的文件,对于它的读写也可以使用普通的read、write等函数。但是它不是普通的文件,并不属于其他任何文件系统,并且只存在于内存中。 + +1.2 命名管道FIFO: + +1)FIFO可以在无关的进程之间交换数据 + +2)FIFO有路径名与之相关联,它以一种特殊设备文件形式存在于文件系统中。 + + + +\2. 系统IPC: + +2.1 消息队列 + +消息队列,是消息的链接表,存放在内核中。一个消息队列由一个标识符(即队列ID)来标记。 (消息队列克服了信号传递信息少,管道只能承载无格式字节流以及缓冲区大小受限等特点)具有写权限得进程可以按照一定得规则向消息队列中添加新信息;对消息队列有读权限得进程则可以从消息队列中读取信息; + +特点: + +1)消息队列是面向记录的,其中的消息具有特定的格式以及特定的优先级。 + +2)消息队列独立于发送与接收进程。进程终止时,消息队列及其内容并不会被删除。 + +3)消息队列可以实现消息的随机查询,消息不一定要以先进先出的次序读取,也可以按消息的类型读取。 + + + +2.2 信号量semaphore + +信号量(semaphore)与已经介绍过的 IPC 结构不同,它是一个计数器,可以用来控制多个进程对共享资源的访问。信号量用于实现进程间的互斥与同步,而不是用于存储进程间通信数据。 + +特点: + +1)信号量用于进程间同步,若要在进程间传递数据需要结合共享内存。 + +2)信号量基于操作系统的 PV 操作,程序对信号量的操作都是原子操作。 + +3)每次对信号量的 PV 操作不仅限于对信号量值加 1 或减 1,而且可以加减任意正整数。 + +4)支持信号量组。 + + + +2.3 信号signal + +信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生。 + + + +2.4 共享内存(Shared Memory) + +它使得多个进程可以访问同一块内存空间,不同进程可以及时看到对方进程中对共享内存中数据得更新。这种方式需要依靠某种同步操作,如互斥锁和信号量等 + +特点: + +1)共享内存是最快的一种IPC,因为进程是直接对内存进行存取 + +2)因为多个进程可以同时操作,所以需要进行同步 + +3)信号量+共享内存通常结合在一起使用,信号量用来同步对共享内存的访问 + + + +3.套接字SOCKET: + +socket也是一种进程间通信机制,与其他通信机制不同的是,它可用于不同主机之间的进程通信。 + + + +线程间通信的方式: + +临界区:通过多线程的串行化来访问公共资源或一段代码,速度快,适合控制数据访问; + +互斥量Synchronized/Lock:采用互斥对象机制,只有拥有互斥对象的线程才有访问公共资源的权限。因为互斥对象只有一个,所以可以保证公共资源不会被多个线程同时访问 + +信号量Semphare:为控制具有有限数量的用户资源而设计的,它允许多个线程在同一时刻去访问同一个资源,但一般需要限制同一时刻访问此资源的最大线程数目。 + +事件(信号),Wait/Notify:通过通知操作的方式来保持多线程同步,还可以方便的实现多线程优先级的比较操作 + +### 5.进程的状态 + +**在一个进程的活动期间至少具备三种基本状态,即运行状态、就绪状态、阻塞状态。** + +![图片](https://mmbiz.qpic.cn/mmbiz_png/J0g14CUwaZcvw4t9kicec370n3cvX2JS9EfRviciaGMLREQ1nqvjWkibKlREGPI9JyfhA5XlmzFRRiaIATAEiaLbCx4w/640?wx_fmt=png&tp=webp&wxfrom=5&wx_lazy=1&wx_co=1)进程的三种基本状态 + +上图中各个状态的意义: + +- 运行状态(*Runing*):该时刻进程占用 CPU; +- 就绪状态(*Ready*):可运行,但因为其他进程正在运行而暂停停止; +- 阻塞状态(*Blocked*):该进程正在等待某一事件发生(如等待输入/输出操作的完成)而暂时停止运行,这时,即使给它CPU控制权,它也无法运行; + +当然,进程另外两个基本状态: + +- 创建状态(*new*):进程正在被创建时的状态; +- 结束状态(*Exit*):进程正在从系统中消失时的状态; + +于是,一个完整的进程状态的变迁如下图: + +![图片](https://mmbiz.qpic.cn/mmbiz_png/J0g14CUwaZcvw4t9kicec370n3cvX2JS9gjKOC2IyZwLJXMcqzgvpKia0u1ezepiawX0iaFkrvsLeV6qsHplv5grnw/640?wx_fmt=png&tp=webp&wxfrom=5&wx_lazy=1&wx_co=1)进程五种状态的变迁 + +再来详细说明一下进程的状态变迁: + +- *NULL -> 创建状态*:一个新进程被创建时的第一个状态; +- *创建状态 -> 就绪状态*:当进程被创建完成并初始化后,一切就绪准备运行时,变为就绪状态,这个过程是很快的; +- *就绪态 -> 运行状态*:处于就绪状态的进程被操作系统的进程调度器选中后,就分配给 CPU 正式运行该进程; +- *运行状态 -> 结束状态*:当进程已经运行完成或出错时,会被操作系统作结束状态处理; +- *运行状态 -> 就绪状态*:处于运行状态的进程在运行过程中,由于分配给它的运行时间片用完,操作系统会把该进程变为就绪态,接着从就绪态选中另外一个进程运行; +- *运行状态 -> 阻塞状态*:当进程请求某个事件且必须等待时,例如请求 I/O 事件; +- *阻塞状态 -> 就绪状态*:当进程要等待的事件完成时,它从阻塞状态变到就绪状态; + +另外,还有一个状态叫**挂起状态**,它表示进程没有占有物理内存空间。这跟阻塞状态是不一样,阻塞状态是等待某个事件的返回。 + +由于虚拟内存管理原因,进程的所使用的空间可能并没有映射到物理内存,而是在硬盘上,这时进程就会出现挂起状态,另外调用 sleep 也会被挂起。 + +![图片](https://mmbiz.qpic.cn/mmbiz_png/J0g14CUwaZcvw4t9kicec370n3cvX2JS9dsvxg4PrqzwaWvVS4CUicfzjAvE4gqHib3duJQPD35CWNibNrzicEP8bwA/640?wx_fmt=png&tp=webp&wxfrom=5&wx_lazy=1&wx_co=1)虚拟内存管理-换入换出 + +挂起状态可以分为两种: + +- 阻塞挂起状态:进程在外存(硬盘)并等待某个事件的出现; +- 就绪挂起状态:进程在外存(硬盘),但只要进入内存,即刻立刻运行; + +这两种挂起状态加上前面的五种状态,就变成了七种状态变迁(留给我的颜色不多了),见如下图: + +![图片](https://mmbiz.qpic.cn/mmbiz_png/J0g14CUwaZcvw4t9kicec370n3cvX2JS9OSw0O4hBZhsvyrPTCkXqwCg9QgtBfdrCsU90NaspiabyILN5QxmAYxQ/640?wx_fmt=png&tp=webp&wxfrom=5&wx_lazy=1&wx_co=1)七种状态变迁 + + + + + + + + + + + +### 7.同一个进程的线程共享哪些资源 + +> 1.进程代码段、 +> +> 2.进程的数据段(利用这些共享的数据,线程很容易的实现相互之间的通讯)、 +> +> 3.进程打开的文件描述符、信号的处理器、进程的当前目录和进程用户ID与进程组ID。 + + + +### 8.同一个进程的线程不共享哪些资源 + +线程ID、寄存器组的值、线程的堆栈、错误返回码、线程的信号屏蔽码、线程的优先级 + + 1.线程ID + 每个线程都有自己的线程ID,这个ID在本进程中是唯一的。进程用此来标识线程。 + + 2.寄存器组的值 + 由于线程间是并发运行的,每个线程有自己不同的运行线索,当从一个线程切换到另一个线程上时,必须将原有的线程的寄存器集合的状态保存,以便将来该线程在被重新切换到时能得以恢复。 + + 3.线程的堆栈 + 堆栈是保证线程独立运行所必须的。 + 线程函数可以调用函数,而被调用函数中又是可以层层嵌套的,所以线程必须拥有自己的函数堆栈,使得函数调用可以正常执行,不受其他线程的影响。 + + 4.错误返回码 + 由于同一个进程中有很多个线程在同时运行,可能某个线程进行系统调用后设置了errno值,而在该线程还没有处理这个错误,另外一个线程就在此时被调度器投入运行,这样错误值就有可能被修改。 + 所以,不同的线程应该拥有自己的错误返回码变量。 + + 5.线程的信号屏蔽码 + 由于每个线程所感兴趣的信号不同,所以线程的信号屏蔽码应该由线程自己管理。但所有的线程都共享同样的信号处理器。 + + 6.线程的优先级 + 由于线程需要像进程那样能够被调度,那么就必须要有可供调度使用的参数,这个参数就是线程的优先级。 + +### 9.**线程从用户态切换到内核态:** + +什么情况下会造成线程从用户态到内核态的切换呢? + +首先,如果在程序运行过程中发生中断或者异常,系统将自动切换到内核态来运行中断或异常处理机制。 + +此外,程序进行系统调用也会从用户态切换到内核态。 + + + +## 2.进程、线程通信方式 + +进程通信方式 + +管道、IPC(消息队列、共享内存、信号量、信号)、socket + +信号量: + +信号量的本质就是一个计数器,用来实现进程之间的互斥与同步。例如信号量的初始值是 1,然后 a 进程来访问内存1的时候,我们就把信号量的值设为 0,然后进程b 也要来访问内存1的时候,看到信号量的值为 0 就知道已经有进程在访问内存1了,这个时候进程 b 就会访问不了内存1。所以说,信号量也是进程之间的一种通信方式。 + + + +链接:https://zhuanlan.zhihu.com/p/104713463 + +为什么共享内存的方式比其他进程间通信的方式效率高? + +消息队列,FIFO,管道的消息传递方式一般为 : + +1).服务器获取输入的信息; + +2).通过管道,消息队列等写入数据至内存中,通常需要将该数据拷贝到内核中; + +3).客户从内核中将数据拷贝到自己的客户端进程中; + +4).然后再从进程中拷贝到输出文件; + +上述过程通常要经过4次拷贝,才能完成文件的传递。 + +而共享内存只需要: + +1).输入内容到共享内存区 + +2).从共享内存输出到文件 + +## 10.互斥、同步、并发和并行 + +### 1.并发 + +在单核 CPU 系统里,为了实现多个程序同时运行的假象,操作系统通常以时间片调度的方式,让每个进程执行每次执行一个时间片,时间片用完了,就切换下一个进程运行,由于这个时间片的时间很短,于是就造成了「并发」的现象。 + +![图片](https://mmbiz.qpic.cn/mmbiz_png/J0g14CUwaZcJWrfgAR82HEMZFficYr34yUqcPlbC7BA8K7uvbjMzlZDYPwYMZEbDxqjqZvU4YmdlxmKJmz6kvsg/640?wx_fmt=png&tp=webp&wxfrom=5&wx_lazy=1&wx_co=1)并发 + +另外,操作系统也为每个进程创建巨大、私有的虚拟内存的假象,这种地址空间的抽象让每个程序好像拥有自己的内存,而实际上操作系统在背后秘密地让多个地址空间「复用」物理内存或者磁盘。 + +![图片](https://mmbiz.qpic.cn/mmbiz_png/J0g14CUwaZcJWrfgAR82HEMZFficYr34ynrF1wCQKkQ7ziaZGqh2gialnS9eIcFPPtQQ4IrMpMs7engDdUuKUm6IQ/640?wx_fmt=png&tp=webp&wxfrom=5&wx_lazy=1&wx_co=1)虚拟内存管理-换入换出 + +如果一个程序只有一个执行流程,也代表它是单线程的。当然一个程序可以有多个执行流程,也就是所谓的多线程程序,线程是调度的基本单位,进程则是资源分配的基本单位。 + +所以,线程之间是可以共享进程的资源,比如代码段、堆空间、数据段、打开的文件等资源,但每个线程都有自己独立的栈空间。 + +![图片](https://mmbiz.qpic.cn/mmbiz_png/J0g14CUwaZcJWrfgAR82HEMZFficYr34ykr1lnBzhNotSn2QqeaK900M2fdR9NRhNJjt7KgTsH4hHX69utfC9iag/640?wx_fmt=png&tp=webp&wxfrom=5&wx_lazy=1&wx_co=1)多线程 + +那么问题就来了,多个线程如果竞争共享资源,如果不采取有效的措施,则会造成共享数据的混乱。 + +### 互斥 + +**保证一个线程在临界区执行时,其他线程应该被阻止进入临界区**,说白了,就是这段代码执行过程中,最多只能出现一个线程。 + +### 同步 + +**就是并发进程/线程在一些关键点上可能需要互相等待与互通消息,这种相互制约的等待与互通信息称为进程/线程同步**。 + +同步与互斥是两种不同的概念: + +- 同步就好比:「操作 A 应在操作 B 之前执行」,「操作 C 必须在操作 A 和操作 B 都完成之后才能执行」等; +- 互斥就好比:「操作 A 和操作 B 不能在同一时刻执行」; + + + +并发:两个或者多个事件在同一时刻发生 + +并行:两个或者多个事件在 ***同一个时间间隔*** 发生。 + + + + + +## 3.死锁 + +当两个线程为了保护两个不同的共享资源而使用了两个互斥锁,那么这两个互斥锁应用不当的时候,可能会造成**两个线程都在等待对方释放锁**,在没有外力的作用下,这些线程会一直相互等待,就没办法继续运行,这种情况就是发生了**死锁**。 + +进程A获取资源的顺序是资源1、资源2,进程B获取资源的顺序是资源2、资源1,进程先获取资源1,给他上锁,然后获取资源2,但是现在资源2已结被进程B上锁了,进程B要获取资源1,资源1被进程A上锁了,就形成了死锁。 + +https://mp.weixin.qq.com/s?__biz=MzUxODAzNDg4NQ==&mid=2247490551&idx=1&sn=21c2f1145baa32c0e501526191d77c54&chksm=f98e5f5dcef9d64be9d2c0bc25d69c8630f10760007a6b0b4f9fae41dfa3b8c5bbca6260d08d&scene=178&cur_album_id=1408057986861416450#rd + +### 1.死锁的四个必要条件 + +- 互斥条件; +- 持有并等待条件; +- 不可剥夺条件; +- 环路等待条件; + +##### 互斥条件 + +互斥条件是指**多个线程不能同时使用同一个资源**。 + +比如下图,如果线程 A 已经持有的资源,不能再同时被线程 B 持有,如果线程 B 请求获取线程 A 已经占用的资源,那线程 B 只能等待,直到线程 A 释放了资源。 + +![图片](https://mmbiz.qpic.cn/mmbiz_png/J0g14CUwaZcUibyDdMtUl4a0Mh4JptI5OyogHIgicpibhaswiathIvb5nWeVKPSfiaZJFiaC8MESxjYRGwtLdP1Xw3BQ/640?wx_fmt=png&tp=webp&wxfrom=5&wx_lazy=1&wx_co=1) + +##### 持有并等待条件 + +持有并等待条件是指,当线程 A 已经持有了资源 1,又想申请资源 2,而资源 2 已经被线程 C 持有了,所以线程 A 就会处于等待状态,但是**线程 A 在等待资源 2 的同时并不会释放自己已经持有的资源 1**。 + +![图片](https://mmbiz.qpic.cn/mmbiz_png/J0g14CUwaZcUibyDdMtUl4a0Mh4JptI5OneC82tLlWEN0rZV0y82IUwbtNz0LApXkC7BBDrRQkroF9lHu7dlIKA/640?wx_fmt=png&tp=webp&wxfrom=5&wx_lazy=1&wx_co=1) + +##### 不可剥夺条件 + +不可剥夺条件是指,当线程已经持有了资源 ,**在自己使用完之前不能被其他线程获取**,线程 B 如果也想使用此资源,则只能在线程 A 使用完并释放后才能获取。 + + +![图片](https://mmbiz.qpic.cn/mmbiz_png/J0g14CUwaZcUibyDdMtUl4a0Mh4JptI5OyYErAFzpQuuhic1N2ah7Th86DF12ibQIWVuia13IaqL8IQNIDv20YcIbA/640?wx_fmt=png&tp=webp&wxfrom=5&wx_lazy=1&wx_co=1) + +##### 环路等待条件 + +环路等待条件指都是,在死锁发生的时候,**两个线程获取资源的顺序构成了环形链**。 + +比如,线程 A 已经持有资源 2,而想请求资源 1, 线程 B 已经获取了资源 1,而想请求资源 2,这就形成资源请求等待的环形图。 + +![图片](https://mmbiz.qpic.cn/mmbiz_png/J0g14CUwaZcUibyDdMtUl4a0Mh4JptI5Oe0DrWrpPyHavLTm023lkkc9ouk8bKDsLBcJyugQhoBs8bqN6zw0esg/640?wx_fmt=png&tp=webp&wxfrom=5&wx_lazy=1&wx_co=1) + + + + + +### 2.请你说一说死锁发生的条件以及如何解决死锁 + + + +死锁是指两个或两个以上进程在执行过程中,因争夺资源而造成的下相互等待的现象。死锁发生的四个必要条件如下: + +互斥条件:进程对所分配到的资源不允许其他进程访问,若其他进程访问该资源,只能等待,直至占有该资源的进程使用完成后释放该资源; + +请求和保持条件:进程获得一定的资源后,又对其他资源发出请求,但是该资源可能被其他进程占有,此时请求阻塞,但该进程不会释放自己已经占有的资源 + +不可剥夺条件:进程已获得的资源,在未完成使用之前,不可被剥夺,只能在使用后自己释放 + +环路等待条件:进程发生死锁后,必然存在一个进程-资源之间的环形链 + +解决死锁的方法即破坏上述四个条件之一,主要方法如下: + +资源一次性分配,从而剥夺请求和保持条件 + +可剥夺资源:即当进程新的资源未得到满足时,释放已占有的资源,从而破坏不可剥夺的条件 + +## 3.如何解决死锁 + +避免死锁问题就只需要破环其中一个条件就可以,最常见的并且可行的就是**使用资源有序分配法,来破环环路等待条件**。 + +资源有序分配法: + +系统给每类资源赋予一个序号,每个进程按编号递增的请求资源,释放则相反,从而破坏环路等待的条件 + +那什么是资源有序分配法呢? + +线程 A 和 线程 B 获取资源的顺序要一样,当线程 A 是先尝试获取资源 1,然后尝试获取资源 2 的时候,线程 B 同样也是先尝试获取资源 1,然后尝试获取资源 2。也就是说,线程 A 和 线程 B 总是以相同的顺序申请自己想要的资源。 + +我们先要清楚线程 A 获取资源的顺序,它是先获取互斥锁 A,然后获取互斥锁 B。 + +所以我们只需将线程 B 改成以相同顺序的获取资源,就可以打破死锁了。 + +![图片](https://mmbiz.qpic.cn/mmbiz_png/J0g14CUwaZcUibyDdMtUl4a0Mh4JptI5O6gOodqCMPk7lXbwlTf1DtiaAw5ROVjeUichmerliaLHaFSmbPr9oS8QUw/640?wx_fmt=png&tp=webp&wxfrom=5&wx_lazy=1&wx_co=1) + + + +### 死锁总结 + +死锁问题的产生是由两个或者以上线程并行执行的时候,争夺资源而互相等待造成的。 + +死锁只有同时满足互斥、持有并等待、不可剥夺、环路等待这四个条件的时候才会发生。 + +所以要避免死锁问题,就是要破坏其中一个条件即可,最常用的方法就是使用资源有序分配法来破坏环路等待条件。 + + + + + + + +## 4.用户态与内核态 + +1. #### 内核态与用户态的区别 + + 1. **内核态与用户态**:**内核态**(系统态)与**用户态**是操作系统的两种运行级别。内核态拥有最高权限,可以访问所有系统指令;用户态则只能访问一部分指令。 + 2. **什么时候进入内核态**:共有三种方式:a、**系统调用**。b、**异常**。c、**设备中断**。其中,系统调用是主动的,另外两种是被动的。 + 3. **为什么区分内核态与用户态**:在CPU的所有指令中,有一些指令是非常危险的,如果错用,将导致整个系统崩溃。比如:清内存、设置时钟等。所以区分内核态与用户态主要是出于安全的考虑。 + +2. #### 什么是系统调用 + + Linux内核中设置了一组用于实现各种系统功能的子程序,称为**系统调用**。用户可以通过系统调用命令在自己的应用程序中调用它们。从某种角度来看,系统调用和普通的函数调用非常相似。区别仅仅在于,系统调用由操作系统核心提供,运行于核心态;而普通的函数调用由函数库或用户自己提供,运行于用户态。 + + 系统调用的机制其核心还是使用了操作系统为用户特别开放的一个**中断**来实现,该中断是程序人员自己开发出的一种正常的异常,这个异常具体就是调用int $0x80的汇编指令,这条汇编指令将产生向量为0x80的编程异常。 + + 产生中断(软中断)后,调用中断处理程序,调用System_call函数,就完成操作系统内核态的调用了。 + + + + + +**内核态和用户态的区别** + +当一个任务(进程)执行系统调用而陷入内核代码中执行时,我们就称进程处于内核状态。此时处理器处于特权级最高的(0级)内核代码。当进程处于内核态时,执行的内核代码会使用当前的内核栈。每个进程都有自己的内核栈。当进程在执行用户自己的代码时,则称其处于用户态。即此时处理器在特权级最低的用户代码中运行。当正在执行用户程序而突然中断时,此时用户程序也可以象征性地处于进程的内核态。因为中断处理程序将使用当前进程的内核态。 + +**用户态切换到内核态的3种方式** + +**a.系统调用** + +这是用户进程主动要求切换到内核态的一种方式,用户进程通过系统调用申请操作系统提供的服务程序完成工作。而系统调用的机制其核心还是使用了操作系统为用户特别开放的一个中断来实现,例如Linux的ine 80h中断。 + +**b.异常** + +当CPU在执行运行在用户态的程序时,发现了某些事件不可知的异常,这是会触发由当前运行进程切换到处理此异常的内核相关程序中,也就到了内核态,比如缺页异常。 + +**c.外围设备的中断** + +当外围设备完成用户请求的操作之后,会向CPU发出相应的中断信号,这时CPU会暂停执行下一条将要执行的指令转而去执行中断信号的处理程序,如果先执行的指令是用户态下的程序,那么这个转换的过程自然也就发生了有用户态到内核态的切换。比如硬盘读写操作完成,系统会切换到硬盘读写的中断处理程序中执行后续操作等。 + + + + + +## 5.HTTP、HTTPS + + + +# 3.计算机网络 + + + +## 1.TCP/IP ⽹络模型 + +应用层:手机 + +传输层:TCP/UDP + +| TCP/IP网络模型 | 功能 | 协议 | | 交互的数据单元 | +| -------------- | ------------------------------------------------------------ | --------------- | ------ | -------------- | +| 应用层 | 应⽤就把应⽤数据传给下⼀层,也就是传输层。 | HTTP、DNS、SMTP | 用户态 | 报文 | +| 传输层 | 为应用层提供网络支持 | TCP、UDP | 内核态 | 数据段 | +| 网络层 | | IP协议、ICMP | | 包 | +| 数据链路层 | 标识⽹络中的设备,让数据在⼀个链路中传输,主要为⽹络层提供链路级别传输的服务 | ARP、RARP | | 帧 | +| 物理层 | 把数据包转换成电信号 | | | | + + + +数据链路层: + +每⼀台设备的⽹卡都会有⼀个 MAC 地址,它就是⽤来唯⼀标识设备的。路由器计算出了下⼀个⽬的地 IP 地址,再 通过 ARP 协议找到该⽬的地的 MAC 地址,这样就知道这个 IP 地址是哪个设备的了。 + + + +## 2.计算机网络体系结构 + +![img](https://uploadfiles.nowcoder.com/images/20210329/675098158_1617019888021/BA6BEB7AE28EF0A97D7A0A038FEB5060) + + + +(1)**应用层**:应用层是体系结构中的最高层。其任务是**通过应用进程间的交互来完成特定网络应用**。应用层协议定义了**应用进程间通信和交互的规则**。应用层协议如域名系统DNS,支持互联网Web应用的协议HTTP,支持电子邮件的协议SMTP等等。应用层交互的数据单元称为**报文**(message)。 + +(2)**传输层**。其任务是为**两台主机中进程之间的通信**提供**通用的数据传输服务**。主要有两种协议:**TCP(Transmission Control Protocol)**提供面向连接的、可靠的数据传输服务,其传输的基本单位是**报文段(segment)**。而**UDP(User Datagram Protocol)**提供无连接的、尽最大努力可靠交付的传输服务,其传输的基本单位是**用户数据报**。 + +(3)**网络层**:其任务是负责为分组交换网上的不同主机提供通信服务。在发送数据时,网络层把**传输层**产生的**报文段**或**用户数据报**封装成**分组(包)(Packet)**进行传送。由于网络层使用IP协议,因此分组也称为IP数据报。网络层的另一个任务就是要**选择合适的路由**,使源主机传输层所传下来的分组,能够通过网络中的**路由器**找到目的主机。**路由(routing)**是指路由器从一个接口上收到数据包,根据数据包的目的地址进行定向并转发到另一个接口的过程。 + +(4)**数据链路层**:两台主机之间的数据传输,总是在一段一段的链路上传送的,这就需要专门的链路层协议。在两个相邻结点之间传送数据时,数据链路层将网络层交下来的IP数据报**组装成帧(framing)**,在两个相邻结点间的链路上传送**帧(frame)**。每一帧包括数据和必要的**控制信息**(如同步信息、地址信息、差错控制等)。用于协作 IP 数据在已有网络介质上传输的协议,典型的是ARP/RARP。 + +**网络接口层包括用于协作 IP 数据在已有网络介质上传输的协议,典型的是ARP/RARP**。 网络接口层就相当于**OSI模型的物理层+数据链路层**,它定义了像 ARP (Address Resolution Protocol ,地址解析协议)这样的协议,提供 TCP/IP 协议的数据结构和实际物理硬件之间的接口 。 + +在接受数据时,控制信息使接受端能够知道一个帧从哪个比特开始到哪个比特结束。这样,数据链路层在收到一个帧后,从中提取数据,上交给网络层。 + +5)**物理层**:物理层传输的数据的单位是比特。传输的是电信号。 + +## 3.各层的协议 + +![img](https://uploadfiles.nowcoder.com/images/20210329/675098158_1617019911262/9EB60BC8BF2B004E4DB7D1CC0D5F1D8C) + +1. **应用层**:应用层是体系结构中的最高层。其任务是**通过应用进程间的交互来完成特定网络应用**。应用层协议定义了**应用进程间通信和交互的规则**。 + + 常用应用层协议如: + + 域名系统**DNS**:DNS协议是用来将域名转换为IP地址(也可以将IP地址转换为相应的域名地址)。 + + 支持互联网应用的协议**HTTP**:超文本传输协议,在浏览器与服务器间传送文档。 + + **SMTP**协议:简单邮件传送协议 + + **FTP**协议:文件传输协议 + + **RIP** 协议:距离矢量路由选择协议。 + +2. **传输层**:其任务是为**两台主机中进程之间的通信**提供**通用的数据传输服务**。 + + 主要有两种协议: + + **TCP:**提供面向连接的、可靠的数据传输服务。 + + **UDP:**提供无连接的、尽最大努力可靠交付的传输服务。 + + **SCTP** (Stream Control Transmission Protocol)是一种传输协议,在TCP/IP协议栈中所处的位置和TCP、UDP类似,兼有TCP/UDP两者特征。 + +3. **网络层**:其**任务是负责为分组交换网上的不同主机提供通信服务**。 + + 常用协议如: + + **IP** 协议:(1) 寻址。(2) 路由选择。(3) 分段与组装。 + + **ICMP**协议:用于在IP主机、路由器之间传递控制消息,用来提供网络诊断信息 。 + +4. **数据链路层**:两台主机之间的数据传输,总是在一段一段的链路上传送的,这就需要专门的链路层协议。 + + **ARP**协议:ARP地址解析协议用于将计算机的网络地址(IP地址32位)转化为物理地址(MAC地址48位) + + **RARP**协议:RARP协议(Reverse ARP,反向ARP协议),其功能是将MAC地址解析为对应的IP地址。 + + + +在**应用层**的数据称为**报文(message)**;在**传输层**的数据称为**段(segment)**;在**网络层**的数据叫 **分组包(packet)**,**网络接口层(链路层)**的数据称为**帧(frame)**。 + +## 4.TCP、UDP + +## 为什么需要TCP? + +IP层不可靠,它不保证⽹络包的交付、不保证⽹络包的按序交付、也不保证⽹络包中的数据的完整性。 + +如果需要保障⽹络数据包的可靠性,那么就需要由上层(传输层)的 TCP 协议来负责 + +因为 TCP 是⼀个⼯作在传输层的可靠数据传输的服务,它能确保接收端接收的⽹络包是⽆损坏、⽆间隔、⾮冗余 和按序的。 + +> ### 什么是 TCP ? + +TCP 是⾯向连接的、可靠的、基于字节流的传输层通信协议。 + +- ⾯向连接:⼀定是「⼀对⼀」才能连接,不能像 UDP 协议可以⼀个主机同时向多个主机发送消息,也就是⼀ 对多是⽆法做到的; + +- 可靠的:⽆论的⽹络链路中出现了怎样的链路变化,TCP 都可以保证⼀个报⽂⼀定能够到达接收端; + +- 字节流:消息是「没有边界」的,所以⽆论我们消息有多⼤都可以进⾏传输。并且消息是「有序的」,当「前 ⼀个」消息没有收到的时候,即使它先收到了后⾯的字节,那么也不能扔给应⽤层去处理,同时对「重复」的 报⽂会⾃动丢弃。 + + + + > ### 什么是 TCP连接? + +⽤于保证可靠性和流量控制维护的某些状态信息,这些信息的组合,包括Socket、序列号和窗⼝ ⼤⼩称为连接。 + +建⽴⼀个 TCP 连接是需要客户端与服务器端达成三个信息的共识。 + + Socket:由 IP 地址和端⼝号组成 + +序列号:⽤来解决乱序问题等 + +窗⼝⼤⼩:⽤来做流量控制 + + + +> ### 如何唯⼀确定⼀个 TCP 连接呢? + +源地址 源端⼝ ⽬的地址 ⽬的端⼝ + + + + + +## 5.TCP、UDP区别 + +1. 连接 + +- TCP 是⾯向连接的传输层协议,传输数据前先要建⽴连接。 + +- UDP 是不需要连接,即刻传输数据。 + +2. 服务对象 + - TCP 是⼀对⼀的两点服务,即⼀条连接只有两个端点。序列号、确认应答、超时重传 + - UDP ⽀持⼀对⼀、⼀对多、多对多的交互通信 + +3. 可靠性 + - TCP 是可靠交付数据的,数据可以⽆差错、不丢失、不重复、按需到达。 + - UDP 是尽最⼤努⼒交付,不保证可靠交付数据。 +4. 拥塞控制、流量控制 + - TCP 有拥塞控制和流量控制机制,保证数据传输的安全性。 + - UDP 则没有,即使⽹络⾮常拥堵了,也不会影响 UDP 的发送速率。 + +5. ⾸部开销 + - TCP ⾸部⻓度较⻓,会有⼀定的开销,⾸部在没有使⽤「选项」字段时是 20 个字节,如果使⽤了「选项」 字段则会变⻓的,开销大。 + - UDP ⾸部只有 8 个字节,并且是固定不变的,开销较⼩。 +6. 传输⽅式 + - TCP 是流式传输,没有边界,但保证顺序和可靠。 + - UDP 是⼀个包⼀个包的发送,是有边界的,但可能会丢包和乱序。 + +7. 分⽚不同 + - TCP 的数据⼤⼩如果⼤于 MSS ⼤⼩,则会在传输层进⾏分⽚,⽬标主机收到后,也同样在传输层组装 TCP 数据包,如果中途丢失了⼀个分⽚,只需要传输丢失的这个分⽚。 + - UDP 的数据⼤⼩如果⼤于 MTU ⼤⼩,则会在 IP 层进⾏分⽚,⽬标主机收到后,在 IP 层组装完数据,接着再 传给传输层,但是如果中途丢了⼀个分⽚,在实现可靠传输的 UDP 时则就需要᯿传所有的数据包,这样传输 效率⾮常差,所以通常 UDP 的报⽂应该⼩于 MTU + +8. 应用场景 + +- TCP提供可靠的服务,适用于通讯质量要求高的场景; +- UDP传输效率高,适用于高速传输和实时性要求的场景。 + +## 6.TCP、UDP应用场景 + +由于 TCP 是⾯向连接,能保证数据的可靠性交付,因此经常⽤于: + +- FTP ⽂件传输 +- HTTP / HTTPS + +由于 UDP ⾯向⽆连接,它可以随时发送数据,再加上UDP本身的处理既简单⼜⾼效,因此经常⽤于: + +- 包总比较少的通信,如 DNS 、 SNMP 等 +- 视频、⾳频等多媒体通信 +- ⼴播通信 + + + + + +## 7.**TCP三次握手与四次挥手** + +![img](https://uploadfiles.nowcoder.com/images/20210329/675098158_1617020287180/602E8F042F463DC47EBFDF6A94ED5A6D) + + + +**三次握手**: + +1. Client将标志位SYN置为1,随机产生一个值seq=J,并将该数据包发送给Server,Client进入SYN_SENT状态,等待Server确认。 +2. Server收到数据包后由标志位SYN=1知道Client请求建立连接,Server将标志位SYN和ACK都置1,ack=J+1,随机产生一个值seq=K,并将该数据包发给Client以确认连接请求,Server进入SYN_RCVD状态。 +3. Client收到确认后,检测ack是否为J+1,ACK是否为1,如果正确则将标志位ACK置为1,ack=K+1,并将该数据包发送给Server,Server检测ack是否为K+1,ACK是否为1,如果正确则连接成功,Client和Server进入ESTABLISHED状态,完成三次握手,随后Client与Server之间可以开始传输数据了。 + +第三次握⼿是可以携带数据的,前两次握⼿是不可以携带数据的, + +**四次挥手**: + +1. 数据传输结束后,Client的应用进程发出连接释放报文段FIN,并停止发送数据,Client进入FIN_WAIT_1状态,此时Client依然可以接收Server发送来的数据。 +2. Server接收到FIN后,发送一个ACK给Client,确认序号为收到的序号+1,Server进入CLOSE_WAIT状态。Client收到后进入FIN_WAIT_2状态。 +3. 当Server没有数据要发送时,Server发送一个FIN报文,此时Server进入LAST_ACK状态,等待Client的确认。 +4. Client收到Server的FIN报文后,给Server发送一个ACK报文,确认序列号为收到的序号+1。此时Client进入TIME_WAIT状态,等待2MSL(MSL:报文段最大生存时间),然后关闭连接。 + + + + + + + + + +## 8.**TCP如何保证可靠性** + +1. **序列号、确认应答、超时重传** + 数据到达接收方,接收方需要发出一个确认应答,表示已经收到该数据段,并且确认序列号,序列号说明了它下一次需要接收的数据序列号,保证数据传输有序。如果发送方迟迟未收到确认应答,那么可能是发送的数据丢失,也可能是确认应答丢失,这时发送方在等待一段时间后进行重传。 + +2. **窗口控制** + TCP会利用窗口控制来提高传输速度,意思是在一个窗口大小内,不用一定等到应答才能发送下一段数据,窗口大小就是无需等待确认而可以继续发送数据的最大值。如果不使用窗口控制,每一个没收到确认应答的数据都要重发。 + 使用窗口控制,如果数据段1001-2000丢失,后面数据每次传输,确认应答都会不停发送序号为1001的应答,表示我要接收1001开始的数据,发送端如果收到3次相同应答,就会立刻进行重发;数据一旦丢失,接收端会一直提醒。 + +3. **拥塞控制** + 如果把窗口定的很大,发送端连续发送大量的数据,可能造成网络的拥堵。为了防止拥堵,进行拥塞控制。 + + (1)**慢启动**:定义拥塞窗口,一开始将该窗口大小设为1,之后每次收到一次确认应答,将拥塞窗口大小*2 + + (2)**拥塞避免**:设置慢启动阈值,一般开始都设为65536。拥塞避免是只当拥塞窗口大小达到这个阈值,拥塞窗口的值不再指数上升,而是+1 + + (3)**快恢复**:将报文段的超时重传看做拥塞,则一旦发生超时重传,我们就将阈值设为当前窗口大小的一半,并且窗口大小变为1,重新进入慢启动过程 + + (4)**快速重传**:3次重复确认应答,立即重传。 + + + +## 9.TCP 第⼀次握⼿ SYN 丢包 + +当客户端发起的 TCP 第⼀次握⼿ SYN 包,在超时时间内没收到服务端的 ACK,就会在==超时重传 SYN 数据包,每次超时重传的 RTO (超时时间)是翻倍上涨的,直到 SYN 包的重传次数到达 tcp_syn_retries默认5 值后,客户端不再发送 SYN 包。== + +## TCP 第⼆次握⼿ SYN、ACK 丢包 + +当 TCP 第⼆次握⼿ SYN、ACK 包丢了后,客户端 ==SYN 包会发⽣超时重 传==,==服务端 SYN、ACK 也会发⽣超时重传==。 + +客户端 SYN 包超时重传的最⼤次数,是由 tcp_syn_retries 决定的,默认值是 5 次;服务端 SYN、ACK 包时重传 的最⼤次数,是由 tcp_synack_retries 决定的,默认值是 5 次。 + +## TCP 第三次握⼿ ACK 丢包 + +在建⽴ TCP 连接时,如果第三次握⼿的 ACK,服务端⽆法收到,则服务端就会短暂处于 SYN_RECV 状态,⽽客户 端会处于 ESTABLISHED 状态。 + +由于服务端⼀直收不到 TCP 第三次握⼿的 ACK,则会==⼀直重传 SYN、ACK 包==,直到重传次数超过 tcp_synack_retries 值(默认值 5 次)后,服务端就会断开 TCP 连接。 + +⽽客户端则会有两种情况: + +- 如果客户端没发送数据包,⼀直处于 ESTABLISHED 状态,然后经过 2 ⼩时 11 分 15 秒才可以发现⼀个「死 亡」连接,于是客户端连接就会断开连接。 +- 如果客户端发送了数据包,⼀直没有收到服务端对该数据包的确认报⽂,则会⼀直重传该数据包,直到中传次 数超过 tcp_retries2 值(默认值 15 次)后,客户端就会断开 TCP 连接。 + + + +## 10.TCP流量控制和拥塞避免、重传机制、滑动窗⼝ + +流量控制:防止发送方发的太快,耗尽接收方的资源,从而使接收方来不及处理 + +**`2.流量控制的一些知识点`** + +> (1)接收端抑制发送端的依据:接收端缓冲区的大小 +> (2)流量控制的目标是接收端,是怕接收端来不及处理 +> (3)流量控制的机制是丢包 + +**`3.怎么样实现流量控制?`** + +> 使用滑动窗口 +> **滑动窗口** +> **`1.滑动窗口是什么?`** +> 滑动窗口是类似于一个窗口一样的东西,是用来告诉发送端可以发送数据的大小或者说是窗口标记了接收端缓冲区的大小,这样就可以实现 +> ps:窗口指的是一次批量的发送多少数据 + +2.为什么会出现滑动窗口? + +在确认应答策略中,对每一个发送的数据段,都要给一个ACK确认应答,收到ACK后再发送下一个数据段,这样做有一个比较大的缺点,就是性能比较差,尤其是数据往返的时间长的时候 + +使用滑动窗口,就可以一次发送多条数据,从而就提高了性能 + +3.滑动窗口的一些知识点 + +(1)接收端将自己可以接收的缓冲区大小放入TCP首部中的“窗口大小”字段,通过ACK来通知发送端 +(2)窗口大小字段越大,说明网络的吞吐率越高 +(3)窗口大小指的是无需等待确认应答而可以继续发送数据的最大值,即就是说不需要接收端的应答,可以一次连续的发送数据 +(4)操作系统内核为了维护滑动窗口,需要开辟发送缓冲区,来记录当前还有那些数据没有应答,只有确认应答过的数据,才能从缓冲区删掉 + +ps:发送缓冲区如果太大,就会有空间开销 + + **`4.滑动窗口的优点`** + +> 可以高效可靠的发送大量的数据 + + + +拥塞控制:防止发送方发的太快,使得网络来不及处理,从而导致网络拥塞 + + + + + +## 11.当在局域网中使用ping时,用到了哪些协议? + +通过DNS协议,将ping后接的域名转换为ip地址。(DNS使用的传输层协议是UDP) + +通过ARP解析服务,由ip地址解析出MAC地址,以在数据链路层传输。 + +ping是为了测试另一台主机是否可达,发送一份==**ICMP**==回显请求给目标主机,并等待ICMP回显应答。(ICMP用于在ip主机、路由器间传递网络是否通畅、主机是否可达等控制信息) + + + +## 12.socket + +![img](https://uploadfiles.nowcoder.com/images/20210329/675098158_1617020573807/586E508F161F26CE94633729AC56C602) + + + + + +## 13.HTTP、HTTPS + + HTTP 请求/响应的**步骤**: + +1. **客户端连接到Web服务器** + 一个HTTP**客户端**,通常是**浏览器**,与**Web服务器**的HTTP端口(默认为80)建立一个**TCP**套接字连接。例如,[http://www.baidu.com。](http://www.baidu.com./) + +2. **发送HTTP请求** + 通过TCP套接字,客户端向Web服务器发送一个文本的请求报文,一个请求报文由请求行、请求头部(header)和请求数据 (可选)3部分组成。 + +3. **服务器接受请求并返回HTTP响应** + Web服务器解析请求,定位请求资源。服务器将资源副本写到TCP套接字,由客户端读取。一个**响应**由状态行、响应头部和响应数据(请求体)3部分组成。 + +4. **处理TCP连接** + 若connection 模式为close,则服务器主动关闭TCP连接,客户端被动关闭连接,释放TCP连接;若connection 模式为keepalive,则该连接会保持一段时间,在该时间内可以继续接收请求; + +5. **解析数据,显示Web界面** + 客户端浏览器首先解析状态行,查看表明请求是否成功的状态代码。然后解析每一个响应头,响应头告知以下为若干字节的HTML文档和文档的字符集。客户端浏览器读取响应数据HTML,并在浏览器窗口中显示。 + + + +## 14.在浏览器地址栏键入URL,按下回车之后会经历以下流程? + +1. 解析 URL,浏览器向 DNS 服务器请求解析该 URL 中的域名所对应的 IP 地址; +2. 解析出 IP 地址后,根据该 IP 地址和默认端口 80,和服务器建立TCP连接; +3. 浏览器发出读取文件(URL 中域名后面部分对应的文件)的HTTP 请求,该请求报文作为 TCP 三次握手的第三个报文的数据发送给服务器; +4. 服务器对浏览器请求作出响应,并把对应的 html 文本发送给浏览器; +5. 释放 TCP连接; +6. 浏览器解析html代码,并请求html代码中的资源,最后对页面进行渲染呈现给用户。 + + + +通过DNS获取IP地址 + +1.解析 URL,浏览器确定了 要请求的Web 服务器和⽂件名, + +2.客户端⾸先会发出⼀个 DNS 请求,问本地域名服务器,如果缓存⾥的表格里面可以找到,则它直接返回 IP 地址。 + +3.如果没有,本地 DNS 会去问它的根域名服务器,根域名服务器是最⾼层次的,它不直接⽤于域名解析,但能指明⼀条道路。 + +4.根 DNS 收到来⾃本地 DNS 的请求后,发现后置是 .com,说:“www.baidu.com 这个域名归 .com 区域管 理”,我给你 .com 顶级域名服务器地址给你,你去问问它吧。” + +5.本地 DNS 收到顶级域名服务器的地址后,发起请求问“顶级域名服务器, 你能告诉我 www.baidu.com 的 IP 地址吗?” + +6.顶级域名服务器说:“我给你负责 www.baidu.com 区域的权威 DNS 服务器的地址,你去问它应该能到”。 + +7.本地 DNS 于是转向问权威 DNS 服务器,www.baidu.com对应的IP是啥呀?baidu.com 的权威 DNS 服务器,它是域名解析结果的原出处,他一看这个是他负责的, + +8.权威 DNS 服务器查询后将对应的 IP 地址 X.X.X.X 告诉本地 DNS。 + +9.本地 DNS 再将 IP 地址返回客户端,客户端获得IP地址 + + + +10.通过 DNS 获取到 IP 后,就可以把 HTTP 的传输⼯作交给操作系统中的协议栈。 + +( 协议栈的内部分为⼏个部分,分别承担不同的⼯作。上下关系是有⼀定的规则的,上⾯的部分会向下⾯的部分委托 ⼯作,下⾯的部分收到委托的⼯作并执⾏。) + +11.应⽤程序(浏览器)通过调⽤ Socket 库,来委托协议栈⼯作。协议栈的上半部分有两块,分别是负责收发数据的 TCP 和 UDP 协议,它们两会接受应⽤层的委托执⾏收发数据的操作。 + +12.协议栈的下⾯⼀半是⽤ IP 协议控制⽹络包收发操作,在互联⽹上传数据时,数据会被切分成⼀块块的⽹络包,⽽ 将⽹络包发送给对⽅的操作就是由 IP 负责的。 + +13.数据报遇到了TCP,加上TCP头部, + + + + + + + + + + + +# 4.简历项目 + + + +## 谷歌测试框架gtest + +1.输出颜色``\033[ \033[0m` + +格式颜色,数据统计,报错信息输出 + +宏定义避免a++,++a,typeof + +执行RUN_ALL_TESTS(),函数里面统计了函数测试的总数和成功的测试数,循环输出结构体功能, Function结构体(函数指针和字符串,存储函数名字),将信息打包成结构体,声名一个结构体数组,将函数拷贝 + +通过RUN_ALL_TESTS()控制这个测试用例,有一个结构体,结构体里面存的函数指针和函数名,声名一个结构体类型的数组,存储每一组测试用例,每次输出一个测试用例,统计结果。 + +EXPECT,输出每一个测试用例的结果,同时统计测试样例的个数和失败的个数 + + + +## 简易聊天系统 + + + +```cpp +struct Msg{ + char name[20]; + char msg[1024]; + pthread_mutex_t mutex; + pthread_cond_t cond; +}; + +// ipc.chat.server.c +struct Msg *shar_memory = NULL; +//服务端 +int main() { + int shmid; + key_t key = ftok(".", 202101); + if ((shmid = shmget(key, sizeof(struct Msg), IPC_CREAT | 0666)) < 0) { + perror("shmget"); + exit(1); + } + + if ((shar_memory = (struct Msg *)shmat(shmid, NULL, 0)) == (struct Msg *)-1) { + perror("shmat"); + exit(-1); + } + //初始化 + memset(shar_memory, 0, sizeof(struct Msg)); + pthread_mutexattr_t m_attr; + pthread_mutexattr_init(&m_attr); + pthread_mutexattr_setpshared(&m_attr, PTHREAD_PROCESS_SHARED); + pthread_mutex_init(&shar_memory->mutex, &m_attr); + //初始化一个互斥锁 //互斥锁标识 //互斥锁属性 + + pthread_condattr_t c_attr; + pthread_condattr_init(&c_attr); + pthread_condattr_setpshared(&c_attr, PTHREAD_PROCESS_SHARED); + pthread_cond_init(&shar_memory->cond, &c_attr); + //初始化条件变量 + + while (1) { + // 对互斥锁上锁,若互斥锁已经上锁,则调用者一直阻塞,// 直到互斥锁解锁后再上锁。 + pthread_mutex_lock(&shar_memory->mutex); + printf("Server got the Mutex!\n"); + //阻塞等待 + pthread_cond_wait(&shar_memory->cond, &shar_memory->mutex); + printf("Server got the cond signal!\n"); + printf("<%s> : %s.\n", shar_memory->name, shar_memory->msg); + memset(shar_memory->msg, 0, sizeof(shar_memory->msg)); + pthread_mutex_unlock(&shar_memory->mutex);// 对指定的互斥锁解锁。 + + } + + return 0; +} + +//ipc.chat.client.c +struct Msg *shar_memory = NULL; + +int main(int argc, char **argv) { + + int opt, shmid;//.,共享内存id + char name[20] = {0}; + while ((opt = getopt(argc, argv, "n:")) != -1) { + switch (opt) { + case 'n': + strcpy(name, optarg); + break; + default: + fprintf(stderr, "Usage : %s -n name\n", argv[0]); + exit(1); + } + } + + key_t key = ftok(".", 202101); + //创建共享内存 + if ((shmid = shmget(key, sizeof(struct Msg), IPC_CREAT | 0666)) < 0) { + perror("shmget"); + exit(1); + } + // 进程将共享内存连接到自身的地址空间 + if ((shar_memory = (struct Msg *)shmat(shmid, NULL, 0)) == (struct Msg *)-1) { + perror("shmat"); + exit(1); + } + + while (1) { + char msg[1024] = {0}; + scanf("%[^\n]s", msg); + getchar();//吃掉缓冲区的回车键 + if (!strlen(msg)) continue;//字符串为空 + while (1) { + if (!strlen(shar_memory->msg)) {//当写入数据前,加锁 + pthread_mutex_lock(&shar_memory->mutex); + } + } + //pthread_mutex_lock(&shar_memory->mutex); + printf("Sending : %s...\n", msg); + strcpy(shar_memory->msg, msg);//向共享内存中写入数据 + strcpy(shar_memory->name, name); + pthread_cond_signal(&shar_memory->cond);//至少唤醒一个等待该条件的线程 + pthread_mutex_unlock(&shar_memory->mutex);// //写入数据后,解锁 + printf("Client signaled the cond\n"); + } + + + + return 0; +} + + + +``` + + + + + +```cpp +pthread_mutex_t mutex; // 互斥锁 +int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);//互斥锁初始化, + +int pthread_mutex_lock(pthread_mutex_t *mutex);//对互斥锁加锁解锁 +int pthread_mutex_unlock(pthread_mutex_t *mutex); + +//线程互斥锁 +//同一时间只有一个线程访问数据。互斥量(mutex)就是一把锁。 + +//多个线程只有一把锁一个钥匙,谁上的锁就只有谁能开锁。当一个线程要访问一个共享变量时,先用锁把变量锁住,然后再操作,操作完了之后再释放掉锁,完成。 + +//当另一个线程也要访问这个变量时,发现这个变量被锁住了,无法访问,它就会一直等待,直到锁没了,它再给这个变量上个锁,然后使用,使用完了释放锁,以此进行。 + +pthread_cond_t cond; // 条件变量 +int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict attr);//初始化 +int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);//用于阻塞当前线程,等待别的线程使用pthread_cond_signal()或pthread_cond_broadcast来唤醒 +pthread_cond_signal();//送一个信号给另外一个正在处于阻塞等待状态的线程,使其脱离阻塞状态,继续执行.如果没有线程处在阻塞等待状态,pthread_cond_signal也会成功返回。 + +//条件变量与互斥量一起使用时,允许线程以无竞争的方式等待特定的条件发生。条件变量是线程可用的另一种同步机制。 + +//条件本身是由互斥量保护的。线程在改变条件状态之前必须产生锁住互斥量,其他线程在获得互斥量之前不会到这种改变,因为互斥量必须在锁定以后才能计算条件。 + +// 共享内存函数(shmget、shmat、shmdt、shmctl) +shmget//得到一个共享内存标识符或创建一个共享内存对象 +shmat//(把共享内存区对象映射到调用进程的地址空间) +``` + + + +```cpp +client +getopt// 获取名字 +key_t ftok( char * fname, int id ) + //函数ftok把一个已存在的路径名和一个整数标识符转换成一个key_t值,称为IPC键值(也称IPC key键值),系统建立IPC通讯(如消息队列、共享内存时)必须指定一个ID值。通常情况下,该id值通过ftok函数得到。 +int shmget( key_t, size_t, flag); +//成功获取到key之后,就可以使用该key作为shmget共享内存方法的进程间通信的key值, 返回共享内存的描述符 +void *shmat(int shm_id, const void *shm_addr, int shmflg); +//第一次创建完共享内存时,它还不能被任何进程访问,shmat()函数的作用就是用来启动对该共享内存的访问,并把共享内存连接到当前进程的地址空间。 +pthread_mutex_lock()//互斥锁上锁 +pthread_cond_signal()//激活等待列表中的线程, +pthread_mutex_unlock()// 解锁 + // pthread_cond_wait,先会解除当前线程的互斥锁,然后挂线线程,等待条件变量满足条件。一旦条件变 量满足条件,则会给线程上锁,继续执行pthread_cond_wait +``` + + + +```cpp +server +pthread_mutexattr_t// 互斥锁 +pthread_mutexattr_init// 初始化互斥锁属性, +pthread_mutexattr_setpshared// 互斥锁属性,指定是该进程与其他进程的同步 +pthread_mutex_init// 动态的创建互斥锁 +pthread_condattr_t //条件变量 +pthread_condattr_init// 初始化条件变量属性 +pthread_condattr_setpshared// 设置条件变量属性 +pthread_cond_init// 动态初始化条件变量 +pthread_cond_wait +``` + + + +实现了进程间通信,使用了共享内存、互斥锁、条件变量,客户端创建(shget)共享内存空间,绑定到(shmat)自身的地址,判断如果输入数据,就加锁pthread_mutex_lock,将信息拷贝共享内存空间,写入数据后,就向服务端发送一个信号pthread_cond_signal(),至少唤醒一个等待该条件的线程,解锁 + +服务端初始化互斥锁pthread_mutex_init、条件变量pthread_cond_init、上锁,阻塞等待pthread_cond_wait,先会解除当前线程的互斥锁,然后挂线线程,等待条件变量满足条件。一旦条件变量满足条件,则会给线程上锁,然后打印输出信息,memset清空内存,对互斥锁解锁 + + + +# 5.菜鸡春招面经成长之路 + + + +## 奇安信一面凉 C++服务器开发 + +由于第一次在牛客上视频面试,提前半小时来了直播间,然后有一个签到, + +签到之后就有面试官了,当时我我也是懵逼了 + +准备了两天 + +上来先做一个自我介绍(主要是说简历) + +然后问我哪块比较熟悉,就是自己觉得学的比较好 + +数据结构 + +说说一下常见的数据结构,介绍一下特点? + +顺序表、链表、栈、队列、树、二叉树、BST、AVL、RBT。。。。 + +说一下红黑树的左旋? + +此时的我已经有点紧张,我当时说了一个右旋方法,后来面试完才想起来 + + + +TCP、UDP优点缺点? + +简单说一下TCP三次握手? + +为什么是三次? + +TCP三次握手过程中有一次失败会发生什么? + +本来就紧张,然后就说了一个超时重传, + +具体说一下,第二次、第三次没了会发生什么? + + + +线程进程共享那些东西、线程独享哪些? + +说一下死锁的四个必要条件? + +用户态和内核态?(看我简历写了) + +输入URL会发生什么,越详细越好? + +脑子已经什么都没有了。。。然后那好吧差不多二十分钟了,就面道这里吧 + +后来面试完问的好简单,只有tcp三次握手没送达会发生什么不会,但是从那里不会就开始有点紧张,后面的就磕磕巴巴又紧张。 + +然后是反问 + +面试官说不要紧张,轻松一点没事。然后问了一个工作具体会做一些什么呀? + +主要是做golang开发的,难怪没有问我c++,推荐大家面试前先看看这个公司的部门,如果可以的话可以了解一下部门是做什么的,可以预判一下面试官会问一些什么问题。二十分钟紧张就结束了 + + + +## 金美通信 C++开发 + + hr面 + +做一下自我介绍 + +说了一遍简历 + +hr:普遍是电脑是吗,可以看出来您是在参考什么? + +笑着说我这边是简历 + + hr:把我的大多数问题都说了,那我们直接问一下其他问题吧 + +学校是一本还是二本? + +学历有问题吗?可以毕业吗? + +家里是哪里人?哪里人支持到北京吗? + +技术面 + +项目 + +虚析构函数为什么必须是虚函数? + +进程间通信方式? + +计算机网络OSI七层模型都有哪些,每一层说一个协议? + +ping用的是哪个协议? + +STL用吗,map,set底层用什么实现? + +socket如何实现通信,都有哪些函数? + +遇到一个不会的问题怎么办? + +虽然有点紧张,但是问的相对简单,所以基本上没什么问题,问的也相对简单 + + + +## 跟谁学|高途课堂 C++开发工程师 + +笔试题简单吧,5个过了三个 + +一面凉凉 + +自我介绍 + +static关键字的作用 + +TCP、UDP区别 + +四次挥手 + +类的默认函数都有哪些? + +类的构造和析构必须是虚函数吗? + +STLvector、dequeue底层实现 + +vector如何释放内存 + +不太理解底层实现是什么鬼?说完vector,让我说dequeue,我说这个不会,好吧今天的面试结束了 + +马上学以一顿,不过这次心态超级好,没有紧张,还有遇到不会的可以往回的方向上说,比如我不太了解deque,可以说我了解set,底层用红黑树实现,搜索、移除、插入是拥有对数时间复杂度,map,底层通常用红黑树实现,他存储的是一个键值对,键值有序的,且唯一, + +还有遇到会的一定要多说一点。(每次一个面试失败的小技巧) + + + + + + + +## 中科创达 + +第一次记错时间了,当时是电话通知,我记到表格上,可能是记错了吧,然后hr和我说的时候我才知道记错了。然后安排第二次面试,然后他说是周一,然后给我发的周二的会议,然后我周二在会议室等了半小时,然后问hr,hr说我们约的是周一,然后我又没到。。。。认命了,可能是没缘分吧。 + + + + + +## 神策数据 + +笔试题忘了是什么,选择题和编程题吧 + +三个编程题都是暴力直接100%,有一个动态规划 + +一面 + +来一个自我介绍 + +先做一道题,在有序数组中去掉重复的数字,返回一共有多少个不重复的数字 + +迷迷糊糊的就过了,面完了也没看懂自己写的代码是什么意思,面试官让我解释代码,我说类似于快慢指针, + +然后巴拉巴拉,他把我代码要走了,估计是过了,但是我说了一遍是什么意思,他可能没听懂。然后也没咋问我。 + +list,vector,set,map,实现原理和应用场景?(上次面试问到的,然后恶补了一下STL,问的都会,半天学会,也不是学会,是复习一下吧) + +static有什么用? + +C++为什么有虚构函数? + +为什么有虚析构函数? + +如何查看内存?是top吗?不会。。 + +那创建文件夹?mkdir + +那说一个从服务器上远程复制吧? scp + +查看ip?ifconfig + +进程间通信都有哪些? + +线程中栈和堆都是共享吗?栈和堆的区别? + +http,https的区别?说了一半,后面的忘了 + +然后提醒了我一下他两的端口都有啥? + +443, 80(这个还不会,这个八股文面试前看见的) + +那问一个实际情况的问题吧?100G限制数据内存100M,求top100? + +典型topK问题,我说先把文件分开吧,他说怎么分,想了好久怎么分, + +那假如已经分开呢,我说那可以用优先队列,排序树中序遍历也可以,100个节点, + +然后反问? + +怎么分啊? + +确实需要分开,用哈希表分,每一个文件放一个单词(我想的是怎么把100G分开。。。方向错了) + +这个工作会做什么呀?主要是大数据java,C++后端写什么高大上的东西我也听不懂,然后我说哦我们学校的课程就上过大数据hadoop,然后我会一些简单的用法 + +面试流程都有什么?两个技术面+hr吧 + +面试官好像挺着急走的,连着问我还有什么问题吗,可能要安排下一个面试吧?我说没有了, + +可能是编程题做的时间比较短,面了40分钟左右就结束了,期待二面,菜鸡的我终于从八股文里面跳出来了 + +神策数据二面 + +因为这是最近的最后一次面试机会了吧,就搞得有点紧张,面试官人很好,一直听说有一种面试官看你紧张就会和帮你缓解一下紧张,我也是第一次遇到,运气真好, + +问了一些vim的基本使用,都是不经常用的,然后我也忘得差不多了,(事后才反应过来应该说点自己会哪些命令) + +说了一个递归创建文件夹,mkdir -p这个地方我说错了,其实紧张还是稍微有一点的 + +我说c++是自己学的,问我跟那里学的,我说就买的那种,然后问我名字,然后我说了,然后还问我有没有别的,我说了一个在B站学java的,有一个狂神说, + +问我vim有没有安装插件,我说了一个最近安装了一个彩虹括号,其实我一般用vimplus,但是我当时就是没想起来了,好尴尬(感觉自己可能凉了吧,害,好事多磨吧) + +问了一些数据,之前学linux命令,最近看到了这本书,我就说了鸟哥linux,然后问我看过哪块,我。。。好尴尬。这个好久没看了,,, + +问了线程进程、僵尸进程,kill -9,kill + +总结:其实面试官也不知道你会哪些,他只能试探你会哪里,然后继续深挖你会的地方,所以当面试官问到你不会的地方的时候,你要==主动表达自己会的地方==,引导面试官来问一些问题,这样证明你在这块学的还可以,让面试官认可你。增加自己面试成功的几率。 + +反问了,他说随便问,? + +第一次遇到这么好的面试官,问了一些职业发展方向,问我为什么自己学c,项目有一个是用的c,其实是学校的,我以为所有的人都会c,我们大一的时候学的c, + +我说c++还挺有意思的,我说学校学的是java,问我c++和java的区别,,都是一些自由的话题吧,不过也能看出一些个人对这个行业的了解和看法,毕竟思想是很重要的东西,我一直觉得 + +最后面试官电脑没电了,其实我还挺想和他唠一会的,面了不到三十分钟吧,被迫结束了。 + + + +## 浙江大华 + +体验不太好,刚开始是微信语言,对面网络不好,然后中途还接了几个电话,然后打电话继续聊,又接了一个电话,然后说我声音小,他问的问题让我产生了很多误解,就是自己想的和对面想的不一样,他说线程,我以为问的是线程函数,因为当时聊到了编程这块,然后其实他是想说线程的定义,我直接来了一个这块不经常用,进程用的多一点,然后他说了一个那说一下进程的定义吧。。。我这才反应过来。自然而然挂掉了。 + +最有意义的一个问题就是不用排序实现求第二个最大的元素? + +## 上海英方软件 + +其实面这家的时候我都没有准备,毕竟面的有点自闭。然而面试官很好,我说对的时候,面试官总会说你这个说的挺好,那下一个。就这样总是会收到对面的反馈。挑了一个项目,问我那个比较好,挑一个,我觉得关于socket的项目能多说一点,然后就选了第二个,然后还问了一些其他的问题,还给我说了答案。第一次被问到很多关于C++的问题,其他的公司C++问的比较少。问到了智能指针,问到了虚函数的原理,我简历上写了,所以问了一下。问了一个阻塞,前面都没有问到,阻塞就是返回当前的一个状态,不管有没有结果。他说我这个答案说的很好。然后就是进程的五个基本状态,我漏掉了一个挂起,他提醒我,我说错了,然后告诉了我答案,挂起。属实忘了,还有死锁的四个必要条件。我答了三个,然后还有一个忘了,他还是说忘了没关系,我给你讲一下。第一次遇到这么好的面试官。其他的问题都是经常问到的。然后反问问了一些公司的基本情况。总之体验很好、 + +一面完事很快就收到了hr电话,说过了,然后说了一些公司的基本情况。说了四十分钟左右吧。然后hr下班了。我有收到了一个电话,说是技术二面,二面问了一些堆,栈,简单吧。上面都有了。说了一些项目的事情。然后说了一些部门都会干些什么。然后二十分钟左右吧,就结束了。下班了。 + +周一收到了offer,hr先是说你通过了技术面,基础过关,但是你没有什么项目,都是课上的小项目。然后我们还是认可你的。就这种,,,套路吧。然后就是说薪资可以给到9.5k,转正1.5k,其实搜过公司的薪资大概是11k,就这样被压了0.5.哈哈不至于吧。通过之后一个小时左右吧收到了小米的调剂信息,说北京急招,问我有没有意向。期待ing. + +期间还面了一些小公司北京凝思、北京新方、拒绝了闻泰科技的面试,有一些公司面试就不会问很多技术问题,更多的是问一些学历和成绩上的问题。做过很多笔试吧,滴滴、海威康视、字节、东信北邮、好未来、WPS、网易、58、完美世界、美团。。。 + +总结:其实找工作也看缘分吧。就比如我吧,一直准备C++的内容,然而C++问的很少,毕竟有时候面试官可能是学别的语言的,然后只能扣其他的了。 + +持续更新ing + + + + + + + + + + + + + diff --git "a/02.c++\347\254\224\350\256\260/15.C++.md" "b/02.c++\347\254\224\350\256\260/15.C++.md" new file mode 100644 index 0000000..d93781f --- /dev/null +++ "b/02.c++\347\254\224\350\256\260/15.C++.md" @@ -0,0 +1,55 @@ +2021.09.06 + +# 1.C99->C++11 + +学习重点 + +C++内容: + +1.继承了C语言的29个头文件(C++一共130个头文件) + +2.引入类和对象 + +3.异常处理 + +4.模板函数 + +5.Lambda(C++11) + +6.引入STL模板 + + + +C++支持面向过程编程、面向对象编程、泛型编程、函数式编程 + +# 2. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +# 0.end \ No newline at end of file diff --git "a/02.c++\347\254\224\350\256\260/16.C++\350\256\276\350\256\241\346\250\241\345\274\217.md" "b/02.c++\347\254\224\350\256\260/16.C++\350\256\276\350\256\241\346\250\241\345\274\217.md" new file mode 100644 index 0000000..04dd8df --- /dev/null +++ "b/02.c++\347\254\224\350\256\260/16.C++\350\256\276\350\256\241\346\250\241\345\274\217.md" @@ -0,0 +1,592 @@ +设计模式:在一定环境下,用固定套路解决问题 + + + +# 1.设计模式 + + + +## 1.设计模式种类 + +GoF提出的设计模式有23个,包括: + +创建型模式:如何创建对象 + +结构型模式:如何实现类或对象的组合 + +行为型模式:类或对象怎样交互以及怎样分配职责 + + + +简单工厂模式不属于GoF23中设计模式 + + + +设计模式目前的种类:GoF23种 + 简单工厂模式 = 24种 + + + +## 2.设计模式有什么用 + + + +更加深入理解面向对象的思想,知道: + +如何将代码分散在几个不通的类中? + +为什么要有接口? + +何谓针对抽象编程? + +何时不应该使用继承? + +如何不修改源代码增加新功能? + +更好地阅读和理解现有类库和其他系统中的源代码? + + + +## 3.如何学好设计模式 + +设计模式的基础:多态 + + + + + +简单工厂模式 + + + +# 2.面向对象设计原则 + +对于面向对象软件系统的设计而言,两个重要的问题 + +1.支持可维护性 + +2.提高系统的可复用性 + +面向对象设计原则为支持可维护性复用而生,这些原则蕴含在很多设计模式中,他们是从许多设计方案中总结出的指导性原则。 + +> 原则的目的 :高内聚、低耦合 + + + +面向对象原则表 + + + +| 名称 | 定义 | +| -------------------------------------------------------- | ------------------------------------------------------------ | +| 单一职责原则
(Single Responsibility Principle,SRP) | 类的职责单一,对外只提供一种功能,而引起类变化的原因都应该只有一个 | +| 开闭原则
(Open-Closed Principle, OCP) | 类的改动是通过增加代码进行的,而不是修改源代码 | +| 里氏代还原则
(Liskov Substitution Principle,LSP) | 任何抽象类出现的地方都可以用他的实现类进行替换,实际就是虚拟机制,语言级别实现面向对象功能 | +| 依赖倒转原则
(Dependece Inversion Principle, DOP) | 依赖于抽象接口,不要依赖具体的实现类,也就是针对接口编程 | +| 接口隔离原则
(Interface Segregation Principle, ISP) | 不应该强迫用户的程序依赖他们不需要的接口方法,一个接口应该改只提供一种对外的功能,不应该把所有的操作都封装到一个接口中去。 | +| 合成复用原则
(Composite Reuse Principle, CRP) | 如果使用继承,会导致父类的任何变换都可能影响到子类的行为。如果使用对象组合,就降低了这种依赖关系。对于继承和组合,优先使用组合. | +| 迪米特法则
(Law of Demeter, LoB) | 一个对象应当对其他对象尽可能少的了解,从而降低各个对象之间的耦合,提高系统的可维护性。例如在一个程序中,各个模块之间相互调用时,通常会提供一个接口来实现。这样其他模块不需要了解另外一个模块的内部实现细节,这样当一个模块内部发生改变事,不会影响其他模块的使用。(黑盒原理) | + + + +## 1.开闭原则代码示例 + +```cpp +#include +using namespace std; +// 开闭原则 对扩展开放,对修改关闭,增加功能是通过增加代码进行实现的,而不是修改代码 + +// 抽象类 计算器 + +class AbstractCaculator +{ +public : + virtual int getResult() = 0; + virtual void setop(int a, int b) = 0; +}; + +// 加法 + +class Plus : public AbstractCaculator +{ + +public: + virtual void setop(int a, int b) + { + this->m_a = a; + this->m_b = b; + } + virtual int getResult() + { + return this->m_a + m_b; + } + + int m_a; + int m_b; +}; + +// 减法 + +int main() { + + AbstractCaculator *cal = new Plus; + cal->setop(10, 20); + cout << cal->getResult() << endl; + + + return 0; +} + +``` + + + +## 2.迪米特法则代码实例 + +```cpp +#include +#include +#include +using namespace std; + +// 最少知识原则 又叫最少知识原则 + +class AbstrBuild +{ +public : + virtual void sale() = 0; +public: + string m_qulity; +}; + + +class buildA : public AbstrBuild +{ +public : + buildA() + { + m_qulity = "high"; + } + + virtual void sale() + { + cout << m_qulity << " is saled" << endl; + } + + // string m_qulity; +}; + +class buildB : public AbstrBuild +{ +public : + buildB() + { + m_qulity = "low"; + // cout << m_qulity << endl; + } + + virtual void sale() + { + cout << m_qulity << "is saled" << endl; + } + + // string m_qulity; +}; + +class mediator +{ +public: + mediator() + { + AbstrBuild *b = new buildA; + v_b.push_back(b); + + b = new buildB; + v_b.push_back(b); + } + ~mediator() + { + for (vector::iterator it = v_b.begin(); + it != v_b.end(); ++it) + { + if (*it != NULL) + { + delete *it; + } + } + } + + // 对外提供接口 + AbstrBuild *findMyBuild(string q) + { + for (vector::iterator it = v_b.begin(); + it != v_b.end(); ++it) + { + if ((*it)->m_qulity == q) + { + return *it; + } + } + return NULL; + } + + vector v_b; +}; + + +int main() { + // 1 + AbstrBuild *a = new buildA; + cout << a->m_qulity << endl; + if (a->m_qulity == "high") + { + a->sale(); + } + + AbstrBuild *b = new buildB(); + if (b->m_qulity == "low") + { + b->sale(); + } + + // + mediator *me = new mediator; + AbstrBuild *bu = me->findMyBuild("high"); + if (NULL != bu) + { + bu->sale(); + } + + return 0; +} + +``` + + + + + +## 3.合成复用原则案例 + +继承和组合优先使用组合 + +```cpp +#include +using namespace std; + +// +class AbsCar +{ +public: + virtual void run() = 0; +}; + +// dahzong +class dazhong : public AbsCar +{ +public: + virtual void run() + { + cout << "da zhong ..." << endl; + } +}; + +class dahuangfeng : public AbsCar +{ +public: + virtual void run() + { + cout << "da zhong ..." << endl; + } +}; + +class person : public dahuangfeng, public dazhong +{ + // s +}; + +class person2 +{ +public: + void SetCar(AbsCar *car) + { + this->car = car; + } + void run() + { + car->run(); + } + ~person2() + { + delete car; + } +public: + AbsCar *car; +}; + +int main() { + person2 p; + p.SetCar(new dahuangfeng); + p.run(); + + return 0; +} + +``` + + + + + +## 4.依赖倒转原则 + +```cpp +#include +using namespace std; + +// 底层 +class BankWorker { +public : + void saveService() { + cout << "存款..." << endl; + } + void payService() { + cout << "支付..." << endl; + } + void tracnferService() { + cout << "转账..." << endl; + } +}; + +// 中层模块 +void doSaveBussiness(BankWorker *worker) { + worker->saveService(); +} + +void doPayService(BankWorker *worker) { + worker->payService(); +} +void doTracnferService(BankWorker *worker) { + worker->tracnferService(); +} + +// 高层 +void test01() { + BankWorker *worker = new BankWorker; + doSaveBussiness(worker); + doPayService(worker); + doTracnferService(worker); +} + +// +class AbstractWorker { +public : + virtual void dbBusiness() = 0; +}; + +// 存款 +class SaveBankWorkrt : public AbstractWorker { +public : + virtual void dbBusiness() { + cout << " 办理存款业务 ..." << endl; + } +}; +// 专门办理付款 +class payBankWorker : public AbstractWorker { + virtual void dbBusiness() { + cout << " 支付业务 ..." << endl; + } +}; +// 。。。。 + +// 中层业务 +void doNewBusiness(AbstractWorker *worker) { + worker->dbBusiness(); +} + +void test02() { + AbstractWorker *pay = new payBankWorker(); + doNewBusiness(pay); + doNewBusiness(new SaveBankWorkrt()); +} + +int main() { + // test01(); + test02(); + + return 0; +} + +``` + + + +# 简单工厂模式 + +# 创建者模型 + + + +# 结构性模型 + +# 行为型模型 + + + + + + + +# 单例模式 + +全局只有一个对象 + +>如何实现单例: +> +>1.构造函数私有化 +> +>2.增加静态私有化的当前类的指针变量 +> +>3.提供一个静态的对外接口,让用户可以获得单例对象 + + + +```cpp +#include +using namespace std; + +class A { + +private : + A() {} + +public : + static A* getInstace() + { + return a; + } +private : + static A *a; +}; + +int main() { + A::getInstace(); + + return 0; +} +``` + + + + + + + +`单例分为懒汉式 和 饿汉式` + + + +> 单例对象释放问题 +> 不需要考虑 + + + +## 1.懒汉式 + + + +```cpp +// 懒汉式 +class singleton_lazy { +private : + singleton_lazy() { plazy = nullptr;} +public : + static singleton_lazy *getInstace() + { + if (nullptr == plazy) + { + plazy = new singleton_lazy(); + } + return plazy; + } +private : + static singleton_lazy *plazy; +}; + +// 类外初始化 +singleton_lazy* singleton_lazy::plazy = nullptr; + +int main() { + singleton_lazy *p1 = singleton_lazy::getInstace(); + singleton_lazy *p2 = singleton_lazy::getInstace(); + + if (p1 == p2) + { + cout << "same memory, is singleton" << endl; + } + else{ + cout << "not singleton" << endl; + } + return 0; +} +``` + + + +## 2.饿汉式 + + + +```cpp +// 饿汉式 + +class singleton_hungry { +private : + singleton_hungry() { pHungry = nullptr; cout << "i am hungry!\n" << endl;} +public : + static singleton_hungry *getInstace() + { + return pHungry; + } +#if 0 + // 不合适 + static void freeSpace() + { + if (pHungry != NULL) + { + delete pHungry; + } + } +#endif + class garbo { + ~garbo() + { + if (pHungry != nullptr) + { + delete pHungry; + } + } + }; +private : + static singleton_hungry *pHungry; + static garbo garbo; +}; + +// 类外初始化 +singleton_hungry* singleton_hungry::pHungry = new singleton_hungry; + +int main() { + singleton_hungry *p3 = singleton_hungry::getInstace(); + singleton_hungry *p4 = singleton_hungry::getInstace(); + if (p3 == p4) + { + cout << "same memory, is singleton" << endl; + } + else{ + cout << "not singleton" << endl; + } + + return 0; +} + +``` + diff --git "a/02.c++\347\254\224\350\256\260/Cpp_Practical_Tutorial.pdf" "b/02.c++\347\254\224\350\256\260/Cpp_Practical_Tutorial.pdf" new file mode 100644 index 0000000..b108830 Binary files /dev/null and "b/02.c++\347\254\224\350\256\260/Cpp_Practical_Tutorial.pdf" differ diff --git "a/02.c++\347\254\224\350\256\260/Google-C++\347\274\226\347\240\201\350\247\204\350\214\203\344\270\255\346\226\207\347\211\210.pdf" "b/02.c++\347\254\224\350\256\260/Google-C++\347\274\226\347\240\201\350\247\204\350\214\203\344\270\255\346\226\207\347\211\210.pdf" new file mode 100644 index 0000000..1468547 Binary files /dev/null and "b/02.c++\347\254\224\350\256\260/Google-C++\347\274\226\347\240\201\350\247\204\350\214\203\344\270\255\346\226\207\347\211\210.pdf" differ diff --git "a/02.c++\347\254\224\350\256\260/\346\230\245\346\213\233.md" "b/02.c++\347\254\224\350\256\260/\346\230\245\346\213\233.md" new file mode 100644 index 0000000..78bf823 --- /dev/null +++ "b/02.c++\347\254\224\350\256\260/\346\230\245\346\213\233.md" @@ -0,0 +1,40 @@ +# 简历 + +联系方式 + +2.59/4.0 + +奖 + +个人技能 + +编程环境Linux + +使用C++实现、C了解Java、Shell + +算法数据结构 + +linux系统编程 + +后端、客户端、服务器 + +C++软件开发工程师 + +手撕红黑树,AC自动机,动态规划 + +搜索算法 + +linux常用命令,实现cout + +多线程编程 + + + + + + + + + + + diff --git "a/02.c++\347\254\224\350\256\260/\351\273\221\351\251\254C++.md" "b/02.c++\347\254\224\350\256\260/\351\273\221\351\251\254C++.md" new file mode 100644 index 0000000..79a6dff --- /dev/null +++ "b/02.c++\347\254\224\350\256\260/\351\273\221\351\251\254C++.md" @@ -0,0 +1,117 @@ +## 1.C++ namespace + + + +```c++ +#include +using namespace std; + +namespace spaceA { + int g_a = 10; + + namespace spaceB { + struct teacher + { + int id; + }; + } +} + + + +int main() +{ + cout << spaceA::g_a << endl; + + // 1 + // spaceA::spaceB::teacher t1; + // using spaceA::spaceB::teacher; + + // 2 + // teacher t1; + using namespace::spaceA::spaceB; + // teacher t1; + // 2 + struct teacher t1; + t1.id = 10; + cout << t1.id << endl; + + +} +``` + + + +## 2.C++ bool + + + +```c++ +// bool 0 falses +// bool 1 true +// 其他值会转换为1, 大小为1个字节 +void test1() +{ + bool flag = true; + cout << flag << endl; + flag = -100; + cout << flag << endl; + cout << sizeof(flag) << endl; +} +``` + + + +## 3.C++三目运算符 + +```c++ +void test4() +{ + int a = 10; + int b = 20; + int c = 0; + + // c + //左值 右值 + c = a < b ? a : b; + cout << c << endl; + + //((a < b) ? a : b) = 60;// error: expression is not assignable + *(a < b ? &a : &b) = 50; + cout << "a = " << a << endl; + + // c++ + c = a < b ? a : b; + cout << c << endl; + ((a < b) ? a : b) = 60; + cout << "b = " << b << endl; +} +``` + + + +4.c++ const + + + +```c++ +void test5() +{ + const int a = 10; + int *p = (int *)&a; //对常量取地址,编译期会对常量取零时空间生成零时地址 + *p = 70; + cout << "a " << a << endl; + cout << "*p " << *p << endl; +}s +``` + + + + + + + + + + +