原课程地址:https://time.geekbang.org/column/article/192053
别人写的笔记:https://www.cnblogs.com/liugangjiayou/category/1680565.html

小记归档

printf函数的格式字符

printf函数的声明:

int printf(const char *format, ...)
format是字符串,包含了要被写入到标准输出stdout的文本,他可以包含嵌入的format标签,format标签可悲后续附加参数中指定的值替换,并按需求进行格式化。
format标签属性是%[flags][width][.precision][length]specifier
附加参数根据不同的format字符串,函数需要不同的附加参数,每个参数对应了一个需要替换的format中的%标签,因此,参数的个数应该和%标签的个数相同;

数据类型

整型
浮点型

  • float
  • double
  • long double

    c语言中的单引号和双引号

    起因:

    单引号用来初始化单个字符,双引号用来初始化字符串?
    char a = "a"warning
    char a = 'a'就不会..待进一步

    搜索:

  1. 含义不同;单引号引起的字符,实际上代表一个整数,该整数对应于字符在对应字符集中的序列值;比如常用的ASCII字符集,'s'的含义其实和十进制数115是相同的;而双引号引起的字符串,代表的是一个指向无名数组起始字符的指针;
  2. 大小不同;单引号引起的字符,大小就是1个字节,双引号引起的字符串,大小是字符个数+1个字节,因为会自动在字符串末尾添加二进制值为0的字符'\0'

    试验

    printf("%d", 'b' - 'a');输出的结果是1,即两个字符相减的结果是一个整型;

    getchar的用法、和scanf的区别

以无符号char强制转换为int的形式返回读取的字符
即将一个字符强制转换为int形式
putchar则与其相反,以无符号 char 强制转换为 int 的形式返回写入的字符,如果发生错误则返回 EOF。

scanf遇到回车停止读取,但不接收回车,此时回车在输入缓冲区中;
getchar()从缓冲区读走一个字符;

scanf()的返回值

如果成功,返回成功匹配和赋值的个数,如果出现错误或遇到文件末尾,则返回EOF;

scanf()的%[]用法

  1. %[]叫做扫描集,用于给字符串或字符数组赋值;
  2. scanf函数以空白字符空格符、制表符或回车符结束对变量的赋值;
  3. 所有变量都已赋值的情况下,回车是对scanf函数的结束,即继续执行下面的语句;

这就引出来问题:

  1. 如果想把空白字符赋值给变量怎么办?
  2. 如果不想用空白字符作为结束,能否用自定的其他字符作为赋值的结束?

此时,%[]扫描集就能解决以上问题:

  1. %[string]只读取string当中的字符: ```python

    include

int main() { char str[100]; printf(“input str:”); scanf(“%[abc]”, str); printf(“str = %s”, str); return 0;

}

输出:abcdef 输出:str = abc

  1. 2. `%[^string]`只读取非string字符(^有取反的作用),换言之,只要出现了string中的字符,就停止赋值;
  2. ```python
  3. #include <stdio.h>
  4. int main() {
  5. char str[100];
  6. printf("input str:");
  7. scanf("%[^abc]", str);
  8. printf("str = %s", str);
  9. return 0;
  10. }
  11. ===
  12. 输入:12a34
  13. 输出:12

引用:
标识符 c++中 using namespace std的作用
https://blog.csdn.net/fsz520w/article/details/82561795

如何让double类型输出时 不显示多余的0

%[长度修饰][类型修饰][输出类型]
例如printf("%0.2f", i)表示输出小数点后两位;

shift-and算法理解

文本串中每个字符的编码情况,记录着模式串的字符的出现情况
参数p带着模式串与字符串之前位数的匹配情况(如果最近1位的结果是1,说明,上一位就是匹配成功的)
p<<1|1code(str[i])分别代表了两个信息:之前的匹配情况和下一位即将匹配内容的出现情况
每个字符的编码长度=模式串的长度
编码方式描述:模式串第i个字符的编码,等于“1左移i位”与“0”进行“逻辑或”运算;是我一开始想复杂了,并不是递推的关系式,只是把1移到固定的位置——
0个字符,就把1左移0位,即把从右向左数的第0位赋值为1,说明该字符在第0位出现了;
i位字符,就把1左移i位,把从右向左数的第i位赋值为1,说明该字符在第i位出现了;
为什么是code的长度是256?
猜测:因为ASCII码有8位,一共能表示256个字符,为了能给所有可能出现的字符提供编码的位置,所以把code的长度定义成了256;
而且code的键是一个“指针”,是一个“地址”;所以本质上讲,对于每一个指向任意字符的地址,都有其对应的code编码,供我们在单模匹配中使用;
编码的形式,我们看到的是十进制形式,但其二进制表示就是我们想要的内容;??有疑问

  1. // str是字符串,p_str是模式串
  2. int shift_and(const char *str, const char *p_str) {
  3. int code[256] = {0}, m = 0; // code是一个长度为256的数组,并且每一位都初始化为0;为什么是256?,m用于记录模式串的长度
  4. // 初始化每一个字符的编码,怎么编的??不懂;编码方式描述:模式串第i个字符的编码,等于“1左移i位”与“上一个字符的编码”进行“逻辑或”运算
  5. for (int i = 0; p_str[i]; i++, m++) {
  6. code[p_str[i]] |= (1 << i);
  7. }
  8. int p = 0;
  9. for (int i = 0; str[i]; i++) {
  10. p = (p << 1 | 1) & code[str[i]];
  11. // 如果 p 所对应的模式串最高位为1,代表匹配成功
  12. if (p & (1 << (m - 1))) return 1;
  13. }
  14. return 0;
  15. }

按位或、按位与、按位异或:

将位对齐,位与位之间进行计算

  • 按位或:两者其中一个是1,按位或的结果就是1,否则是0
  • 按位与:只有两者同时是1,按位与的结果才是1,否则是0
  • 按位异或:两者不一样,按位异或的结果就是1,否则是0

课程笔记

30 自己编写简易计算器

二叉树的三种遍历方式

  • 前序遍历:根节点 { 前序遍历左子树 } { 前序遍历右子树 }
  • 中序遍历:{ 中序遍历左子树 } 根节点 { 中序遍历右子树 }
  • 后序遍历:{ 后序遍历左子树 } { 后序遍历右子树 } 根节点

仔细观察上面的遍历定义,是一种“递归”定义方式;即{ 前序遍历左子树 }中,把左子树的子节点看做根节点,继续遵循前序遍历的原则进行遍历;

表达式树与后序遍历

任何一个四则运算的表达式,都可以表现为一个二叉树
而表达式计算的值,就是对应的二叉树**后序遍历**的结果
用后序遍历的思路去做递归,代码体现就是:

  1. // 假设函数get_val可以计算字符串表达式String的结果
  2. // op代表string表达式中,最后一个计算的运算符
  3. // 对于任1字符串表达式,都可以写成下述形式:
  4. get_val(String) = get_val(L_String) op get_val(R_String)

如何确定运算的顺序?

针对上面的计算逻辑:“确定当前表达式中,最后一个计算的运算符”就显得尤为重要
如何确定运算符之间的优先级?——通过给所有的运算符附上“计算权重”
对于括号对运算符计算顺序的改变,可以“遇到左括号”就“给运算符加上一个很大的权重”,相反,“遇到右括号”就给运算符减去相同数量的权重
同时,设置一个标签,用于记录当前最低运算优先级的位置;(需在该位置将字符串切分为两个字符串)

  1. #include <stdio.h>
  2. #include <string.h>
  3. #define INF 0x3f3f3f3f
  4. // 计算表达式的值
  5. int calc(const char* String, int l, int r) {
  6. // 查找当前表达式的根节点
  7. // pos记录当前根节点的位置,初始化为-1
  8. // priority 记录根节点的权重等级
  9. // temp 记录括号带来的权重增加
  10. int pos = -1, priority = INF - 1, temp = 0;
  11. for (int i = l; i <= r; i++) {
  12. int cur_priority = INF;
  13. switch(String[i]) {
  14. case '(' : temp += 100; break;
  15. case ')' : temp -= 100; break;
  16. case '+' :
  17. case '-' : cur_priority = 1 + temp; break;
  18. case '*' :
  19. case '/' : cur_priority = 4 + temp; break;
  20. default : break;
  21. }
  22. // 判断当前运算符的优先级
  23. // 如果其小于根节点的运算符,就将其设置为新的根节点
  24. if (cur_priority <= priority) {
  25. pos = i;
  26. priority = cur_priority;
  27. // printf("update priority: %c, %d, priority is %d\n", String[pos], pos, priority);
  28. }
  29. }
  30. // 如果当前根节点就是优先级最低的根节点,说明当前表达式中没有运算符?则计算
  31. if (pos == -1) {
  32. int sum = 0;
  33. for (int i = l; i <= r; i++) {
  34. // 判断 如果有空格之类的,直接跳过
  35. if (String[i] < '0' || String[i] > '9') continue;
  36. sum = sum * 10 + (String[i] - '0');
  37. }
  38. // printf("num = %d\n", sum);
  39. return sum;
  40. }
  41. int a = calc(String, l, pos - 1);
  42. int b = calc(String, pos + 1, r);
  43. switch(String[pos]) {
  44. case '+' : printf("%d + %d = %d\n\n", a, b, a + b); return a + b;
  45. case '-' : printf("%d - %d = %d\n\n", a, b, a - b); return a - b;
  46. case '*' : printf("%d * %d = %d\n\n", a, b, a * b); return a * b;
  47. case '/' : printf("%d / %d = %d\n\n", a, b, a / b); return a / b;
  48. }
  49. return 0;
  50. }
  51. int get_value(const char *str) {
  52. return calc(str, 0, strlen(str) - 1);
  53. }
  54. int main() {
  55. char str1[100];
  56. char a[] = "2+(1+3)*2";
  57. printf("%s = %d\n", a, get_value(a));
  58. while (scanf("%[^\n]", str1) != EOF) { // 只读取非回车字符
  59. getchar(); //getchar在此处的作用是什么?猜测是清空掉空格
  60. //测试了一下,如果不写getchar,回车后,
  61. //程序会不断循环计算第一次输入的公式
  62. //但,具体是如何实现的呢?
  63. //分析:getchar读走了scanf停止读入时,留在输入缓存区里的\n
  64. //如果没有getchar,缓存区中一直有内容
  65. //scanf的结果就一直!=EOF,就陷入了死循环
  66. printf("%s = %d\n", str1, get_value(str1));
  67. }
  68. return 0;
  69. }

如何实现定义变量?未解决

只需要将26个小写字母作为变量,可以定义一个结构体?
利用小写字符的ascii码,将其与数组的i对应起来?(a的ASCII码是97)

定义一个结构体,用于储存定义的变量和对应的值

结构体内有两个成员

  • 字符型的名称
  • 整型的

尝试使用结构体:

  1. #include <stdio.h>
  2. struct new_number //定义结构体
  3. {
  4. char name; // 结构体成员
  5. int value; // 结构体成员
  6. };
  7. int main() {
  8. struct new_number a = {'a', 1}; // 结构体实例初始化
  9. printf("a.name = %c, a.value = %d", a.name, a.value); // 结构体成员的访问
  10. return 0;
  11. }

将结构体融入之前的代码:

01 先尝试无输入,直接在程序内部初始化结构体:
需要有一个“把变量替换成数值”的过程——遍历表达式中的每个字符,如果是26个字母中的一个,就替换成对应的数值
问题:如何根据结构体的某个成员,访问结构体的另一个成员?(有点像对字典中内容的访问)?

感觉实现起来有些麻烦,决定再去看看关于“链表”的内容
(其中的7.2讲述的是简单链表简单链表)
为什么要尝试链表?可以动态管理,在计算器中,不确定用户想要输入几个变量;

简单链表的尝试

  1. #include <stdio.h>
  2. struct student {
  3. long number;
  4. float score;
  5. struct student *next;
  6. };
  7. int main () {
  8. struct student a, b, c, *head, *p;
  9. a.number = 10001, a.score = 99.8;
  10. b.number = 10002, b.score = 98.1;
  11. a.next = &b, b.next = &c, c.next = NULL;
  12. head = &a;
  13. p = head;
  14. while(p != NULL) {
  15. printf("number: %ld\nscore: %.1f\n", p->number, p->score);
  16. p = p->next;
  17. }
  18. return 0;
  19. }

如何实现链表的动态管理?未解决

scanf的类型说明符``%[]

char const *str
scanf("%[]", str)
%[]在scanf中用于扫描字符集合
scanf("%[^\n]", str)语句的作用是碰见了回车就把缓冲区的内容按字符串格式存入到str指向的地址中,但此时回车还留在缓冲区内;ps.^是异或运算符,不同则结果为1,相同则结果为0;

29

28

27 测试框架

26 算法篇小结

25动态规划(下)

多重背包

解析:(c++版本)

  1. #include <stdio.h>
  2. #define N 100
  3. #define max(a, b) (a) > (b) ? (a) : (b)
  4. int m, C, dp[N][N]; // 分别是数量、背包容量和能拿的最大价值
  5. int w[N] = {0, 4, 3, 12, 9}, n[N] = {0, 5, 4, 2, 7}, v[N] = {0, 10, 7, 12, 8}; // 分别是重量、数量、价值
  6. int new_m;
  7. int new_w[N], new_n[N], new_v[N];
  8. // 写在函数里,发现不行...但写在主程序里的却可以
  9. int get_dp(int n, int W, int * n_w, int * n_v) {
  10. // 初始化 dp[0] 阶段
  11. for (int i = 0; i <= W; i++) dp[0][i] = 0;
  12. // 假设 dp[i - 1] 成立,计算得到 dp[i]
  13. // 状态转移过程,i 代表物品,j 代表背包限重
  14. for (int i = 1; i <= n; i++) {
  15. for (int j = 0; j <= W; j++) {
  16. // 不选择第 i 种物品时的最大值
  17. dp[i][j] = dp[i - 1][j];
  18. // 与选择第 i 种物品的最大值作比较,并更新
  19. if (j >= n_w[i] && dp[i][j] < dp[i - 1][j - n_w[i]] + n_v[i]) {
  20. dp[i][j] = dp[i - 1][j - n_w[i]] + n_v[i];
  21. }
  22. }
  23. }
  24. return dp[n][W];
  25. }
  26. int main() {
  27. m = 18;
  28. // 二进制拆分
  29. new_m = 0;
  30. for (int i = 1; i < m; i++) {
  31. for (int j = 1; j <= n[i]; j <<= 1) {
  32. n[i] -= j; // 减去拆掉的内容
  33. new_w[++new_m] = w[i] * j; // 新重量
  34. new_v[new_m] = v[i] * j; // 新价值
  35. }
  36. if (n[i]) { // 余下的是余数,计算余数的重量和价值
  37. new_w[++new_m] = w[i] * n[i];
  38. new_v[new_m] = v[i] * n[i];
  39. }
  40. }
  41. // 打印二进制分配后的物品
  42. for (int i = 0; i < 20; i++) printf("%3d %3d\n", new_v[i], new_w[i]);
  43. printf("new_m = %d\n", new_m);
  44. // 0/1背包
  45. // int ans = get_dp(new_m, 30, new_w, new_n);
  46. // printf("ans = %d\n", ans);
  47. C = 15;
  48. // 初始化物品为0的阶段
  49. for (int i = 0; i <= C; i++) dp[0][i] = 0;
  50. // 假设dp[i - 1]成立,计算dp[i]
  51. for (int i = 0; i <= new_m; i++) {
  52. for (int j = 1; j <= C; j++ ) {
  53. dp[i][j] = dp[i - 1][j];
  54. if (j >= new_w[i] && dp[i][j] < dp[i - 1][j - new_w[i]] + new_v[i]) {
  55. dp[i][j] = dp[i - 1][j - new_w[i]] + new_v[i];
  56. }
  57. }
  58. }
  59. printf("%d", dp[new_m][C]);
  60. return 0;

24 动态规划(上)

动态规划问题的四步骤:

  1. 状态定义 自变量、因变量的表示方法
  2. 状态转移方程
  3. 正确性证明
  4. 程序设计及实现 ```c

    include

    define max(a, b) ((a) > (b) ? (a) : (b))

int tgle [7][7] = { {0}, {0, 3}, {0, 9, 5}, {0, 4, 2, 1}, {0, 3, 4, 9, 6}, {0, 3, 5, 3, 7, 3}, {0, 2, 1, 3, 9, 3, 2}, };

// 递推写法 int dp(int i, int j) { if (i == 0) return tgle[i][j]; return max(dp(i - 1, j - 1), dp(i - 1, j)) + tgle[i][j]; }

// 循环写法 int s[50][50] = {0}; int dp1(int i, int j) { if (i == 0) return tgle[i][j]; for (int m = 1; m <= i; m++) { for (int n = 1; n <= 10; n++ ) { s[m][n] = max(s[m - 1][n - 1], s[m - 1][n]) + tgle[m][n]; } } return s[i][j]; }

int main() { printf(“%d\n”, tgle[0][4]); printf(“%d, %d\n”, dp(2, 2), dp1(2, 2)); return 0;

} // max(dp(0, 0), dp(0, 1) + tgle[1][1]) = max(3, 0 + 0) = 3

  1. <a name="NC0mA"></a>
  2. ### 23 容拆原理与递推算法
  3. 如果想更深入地了解一下容斥原理,可以通过学习莫比乌斯函数、狄利克雷卷积与莫比乌斯反演等内容,进一步感受一下这个思想所绽放出的光芒。
  4. ```c
  5. // 凑钱
  6. #include <stdio.h>
  7. int val[8] = {0, 1, 2, 5, 10, 20, 50, 100};
  8. int find_combs (int i, int j) {
  9. if (j == 0 || i > j) return 1; // 为什么j=0的时候 要return 1;呢?
  10. if (i == 1 && (j % val[1] == 0)) return 1;
  11. return find_combs(i - 1, j) + find_combs(i, j - val[i]);
  12. }
  13. int main () {
  14. int a = find_combs(3, 1);
  15. printf("%d\n", a);
  16. printf("%d\n", val[3]);
  17. return 0;
  18. }

22 单调栈、木板切割

括号有效性 leetcode关于此题的讲解:leetcode

  1. #include <stdio.h>
  2. #include <string.h>
  3. //读入有括号,返回对应的左括号,如果读入的是左括号,就返回0
  4. char pair(char a) {
  5. if (a == ']') return '[';
  6. if (a == '}') return '{';
  7. if (a == '>') return '<';
  8. if (a == ')') return '(';
  9. return 0;
  10. }
  11. //利用栈,检查是否匹配;
  12. //先将左括号依次填入栈
  13. //对于每个右括号,返回其匹配的左括号,如果此时栈内不为空,且栈顶的左括号与返回的左括号相同,则匹配成功
  14. //检查下一个栈顶;
  15. int isValid(char *s) {
  16. int n = strlen(s);
  17. char stk[n + 1], top = 0;
  18. for (int i = 0; i < n; i++) {
  19. char ch = pair(s[i]);
  20. if (ch) {
  21. if(top == 0 || stk[top - 1] != ch) return 0;
  22. top--;
  23. } else {
  24. stk[top++] = s[i];
  25. }
  26. }
  27. return 1;
  28. }
  29. int main() {
  30. char s[4] = "<(>";
  31. int ans = isValid(s);
  32. printf("%d", ans);
  33. }

太难了 示例代码都看不懂未解决

姑且看懂了一些
ps条件表达式? :
bool ? a : b
条件bool是否成立?如果成立,则值为a,否则为b

  1. #define MAX_N 1000
  2. #define max(a, b) ((a) > (b) (a) : (b))
  3. int s[MAX_N + 5], top;
  4. int l[MAX_N + 5], r[MAX_N + 5];
  5. int max_matrix_area(int *h, int n) {
  6. h[0] = h[n + 1] = -1;
  7. top = -1, s[++top] = 0;
  8. // 找到每一块木板,左边第一块比其矮的木板编号
  9. // 如果栈顶指向的木板比i长,就看栈里下一块木板(从底至顶单调递增栈)
  10. // 直到找到第一块比i短的木板,记录在l[i]中,就是左侧第一块比其矮的木板
  11. // 并通过s[++top] = i将其记录在栈顶
  12. // 疑问:单调递增栈是何时形成的?-是通过s[++top] = i形成的
  13. // 单独看while循环和s[++top] = i,意思是:从顶到底遍历栈,当遍历项长度小于当前i长度时,将i放在该项之上(就是形成递增栈的过程)
  14. for (int i = 1; i <= n; i++) {
  15. while (top >= 0 && h[s[top]] >= h[i]) --top;
  16. l[i] = s[top];
  17. s[++top] = i;
  18. }
  19. // 找到每一块木板,右边第一块比其矮的木板编号
  20. top = -1, s[++top] = n + 1;
  21. for (int i = n; i >= 1; i--) {
  22. while (top >= 0 && h[s[top]] >= h[i]) --top;
  23. r[i] = s[top];
  24. s[++top] = i;
  25. }
  26. // 在所有木板中,找到面积最大的矩形
  27. int ans = 0;
  28. for (int i = 1; i <= n; i++) {
  29. ans = max(ans, (r[i] - l[r] - 1) * h[i]);
  30. }
  31. return ans;
  32. }

21 单调队列 滑动区间最大值

  1. #include <stdio.h>
  2. #define MAX_N 1000
  3. int q[MAX_N + 5], head, tail;
  4. //a指向原始队列,m是单调队列长度,n是原始队列总个数
  5. void invertal_max_number(int *a, int n, int m) {
  6. head = tail = 0;
  7. for (int i = 0; i < n; i++) {
  8. //形成初始队列,将不符合单调性的都踢出去
  9. while (head < tail && a[q[tail - 1]] < a[i]) tail--;
  10. //直到维护了单调性,i可以入队了
  11. q[tail++] = i;
  12. //判断队列长度是否超出了范围
  13. if(i - m == q[head]) head++;
  14. //输出区间内的最大值,i+1大于区间长度,确保第一个区间已经形成
  15. if(i + 1 >= m) {
  16. printf("invert(%d, %d)=", i - m + 1, i);
  17. printf("%d\n", a[q[head]]);
  18. printf("q[head]=%d, q[tail]=%d\n\n", q[head], q[tail]);
  19. }
  20. }
  21. return ;
  22. }
  23. int main() {
  24. int a[8] = {1, 3, -1, -3, 5, 3, 6, 7};
  25. invertal_max_number(a, 8, 3);
  26. return 0;
  27. }

20 二分查找

二进制右移>>通常可以理解为除以2(大部分情况下成立)

练习:通过税后工资 反推税前工资

思路:读入工资-二分法查找所在区间-反推税前工资-

  1. #include <stdio.h>
  2. //计算税后工资的函数
  3. double salary_aft(double salary) {
  4. if (salary <= 3000) return salary * 0.97;
  5. if (salary <= 12000) return (salary - 3000) * 0.9 + 3000 * 0.97;
  6. if (salary <= 25000) return (salary - 12000) * 0.8 + 3000 * 0.97 + 9000 * 0.9;
  7. if (salary <= 35000) return (salary - 25000) * 0.75 + 3000 * 0.97 + 9000 * 0.9 + 13000 * 0.8;
  8. return -1;
  9. }
  10. //二分查找
  11. double binary_search(double x, double l, double r) {
  12. double mid;
  13. while (l < r) {
  14. mid = (l + r) / 2.0;
  15. if (salary_aft(mid) == x) return mid;
  16. if (salary_aft(mid) < x) l = mid;
  17. r = mid;
  18. }
  19. return -1.0;
  20. }
  21. int main () {
  22. double y;
  23. y = binary_search(23000.0, 0.0, 35000.0);
  24. printf("%.2f\n", y);
  25. return 0;
  26. }

分析:永远返回-1,是因为无论循环多少次,结束循环的时候都会return -1;
解决办法:改成return mid;当循环结束的时候,l == r == mid,也就是我们要找的值,直接返回mid即可;
可运行的代码如下:
bug1:输入小于3000的数字,会让程序陷入无线循环;
bug2:输入最后一个梯度的数字,计算是错误的,如下图:
image.png
分析原因,无限循环和 segmentation fault错误,是因为double的精度有限,不可能无限的循环下去,因此需要以double类型的精度为基准,为循环设置条件——一般我们认为浮点数的误差在10的负七次方以下的时候,就认为两个数字相等,因此将while循环的条件做更改:
while(r < l)改为while(r - l >= 1e-7)
并且在起始位置增加条件if(r - l <= 1e-7) return r;
最终可运行的代码如下:

  1. #include <stdio.h>
  2. //计算税后工资的函数
  3. double salary_aft(double salary) {
  4. if (salary <= 3000) return salary * 0.97;
  5. if (salary <= 12000) return (salary - 3000) * 0.9 + 3000 * 0.97;
  6. if (salary <= 25000) return (salary - 12000) * 0.8 + 3000 * 0.97 + 9000 * 0.9;
  7. if (salary <= 35000) return (salary - 25000) * 0.75 + 3000 * 0.97 + 9000 * 0.9 + 13000 * 0.8;
  8. return -1;
  9. }
  10. //二分查找
  11. double binary_search(double x, double l, double r) {
  12. double mid;
  13. if (r - l <= 1e-7) return r;
  14. while (r - l >= 1e-7) {
  15. mid = (l + r) / 2.0;
  16. if (salary_aft(mid) == x) return mid;
  17. if (salary_aft(mid) < x) l = mid;
  18. if (salary_aft(mid) > x) r = mid;
  19. }
  20. return mid;
  21. }
  22. int main () {
  23. double x, y;
  24. printf("请输入税后工资:");
  25. scanf("%lf", &x);
  26. y = binary_search(x, 0.0, 35000.0);
  27. printf("税前工资是:%.2f\n", y);
  28. printf("验证计算税后工资:\n");
  29. printf("%.2f", salary_aft(y));
  30. return 0;
  31. }
  1. #include <stdio.h>
  2. //计算税后工资的函数
  3. double salary_aft(double salary) {
  4. if (salary <= 3000) return salary * 0.97;
  5. if (salary <= 12000) return (salary - 3000) * 0.9 + 3000 * 0.97;
  6. if (salary <= 25000) return (salary - 12000) * 0.8 + 3000 * 0.97 + 9000 * 0.9;
  7. if (salary <= 35000) return (salary - 25000) * 0.75 + 3000 * 0.97 + 9000 * 0.9 + 13000 * 0.8;
  8. return -1;
  9. }
  10. //二分查找,递归思维
  11. double binary_search(double x, double l, double r) {
  12. double mid = (l + r) / 2.0;
  13. if (r - l <= 1e-7) return l;
  14. if (salary_aft(mid) == x) return mid;
  15. if (salary_aft(mid) < x) return binary_search(x, mid, r);
  16. if (salary_aft(mid) > x) return binary_search(x, l, mid);
  17. return -1.0;
  18. }
  19. int main () {
  20. double x, y;
  21. printf("请输入税后工资:");
  22. scanf("%lf", &x);
  23. y = binary_search(x, 0.0, 35000.0);
  24. printf("税前工资是:%.2f\n", y);
  25. printf("验证计算税后工资:\n");
  26. printf("%.2f", salary_aft(y));
  27. return 0;
  28. }

已解决

二分法切绳子

  1. #include <stdio.h>
  2. #include <math.h>
  3. //初始化记录,原始的绳子长度和数量
  4. double l[100], n;
  5. //切绳子函数,接收参数:想要的绳子长度x,返回参数:能切出的最多个数y
  6. int f(double x) {
  7. int m = 0;
  8. for(int i =0; i < n - 1; i++) {
  9. m += (int)floor(l[i] / x);
  10. }
  11. return m;
  12. }
  13. //二分法查找
  14. double binary_search(double l, double r, int k) {
  15. if (r - l <= 1e-7) return r;
  16. double mid = (l + r) / 2.0;
  17. if (f(mid) < k) return binary_search(l, mid, k);
  18. return binary_search(mid, r, k);
  19. }
  20. int main() {
  21. double l[3] = {4.0, 5.0, 6.0};
  22. int n = 3;
  23. double m;
  24. m = binary_search(l[0], l[2], 4);
  25. printf("%f\n", m);
  26. }

19 运用链表思维解决快乐数问题

未解决

18 链表

链表的插入,为什么我理解的总是错位?

  1. struct Node *insert(struct Node *head, int ind, struct Node *a) {
  2. struct Node ret, *p = &ret;
  3. ret.next = head;
  4. // 从【虚拟头节点】开始向后走 ind 步
  5. while (ind--) p = p->next; //这里用的ind--,而不是--ind;即先使用ind的值,再进行--
  6. // 完成节点的插入操作
  7. a->next = p->next;
  8. p->next = a;
  9. // 返回真正的链表头节点地址
  10. return ret.next;
  11. }

image.png
再仔细看,不是删除了两个,而是比我写的向后删除了一个
我的代码是p = p->next;有疑问

  1. #include <stdio.h>
  2. //定义链表节点结构
  3. struct Node {
  4. int data;
  5. struct Node *next;
  6. };
  7. //定义链表的插入方法
  8. struct Node *insert(struct *head, int ind, struct Node *a) {
  9. struct Node ret, *p = &ret;
  10. ret.next = head;
  11. //从虚拟头结点向后走ind步
  12. while(ind--) p = p->next;//此处ind还是越来ind的值,先用后减
  13. //完成节点插入的操作
  14. a->next = p->next;
  15. p->next = a;
  16. //返回链表头结点的位置
  17. return ret.next;
  18. }
  19. //定义链表的删除方法
  20. struct Node *erase(struct Node *head, int ind) {
  21. struct Node ret, *p = &ret;
  22. ret.next = head;
  23. //思路:向后走ind步,完成节点的删除操作
  24. while(ind--) p->next;
  25. p = p->next;
  26. return ret.next;
  27. }

17

结合使用shift-and算法和大整数表示法未解决

16

实现大整数的相加

函数没能改变main函数中定义的数组a[]
之前明明实现了,是因为我把函数从void改成了int吗?不是
好像是因为在函数中又定义了一个同名数组a[];是的
现在把a[]的定义放在所有代码的最前面,并且不在函数中定义同名的数组,函数所更改的就是最前面定义的a[];
如何实现在main中定义,在函数中修改、或者 不需要定义,直接在函数中返回呢?未解决

c语言不允许返回数组作为函数的参数,但是通过指定不带索引的数组名来返回一个指向数组的指针,可以通过该指针访问数组

strlen

  • 只能用来计算字符串或字符数组的长度;且不计算结尾\0在内

    c语言 字符和数字的相互转换:

  • int类型加‘0’变字符

  • char类型减‘0’变int

    1. char b = '5';
    2. printf("%d", b - '0');

    https://blog.csdn.net/qq742143797/article/details/107450976

    如何不用len函数”计算数组的长度“或”遍历数组“?未解决

  • 好像是判断是否是\0,c语言中的字符串以\0结尾;

    15

    用筛法求解积性函数

    真的废了不少劲啊…. ```c

    include

    include

define MAX_N 10000

int prime[MAX_N + 5] = {0};

void init_prime() { for (int i = 2; i i <= MAX_N; i++) { if(prime[i]) continue; for (int j = 2 i; j < MAX_N; j +=i) { if (prime[j]) continue; prime[j] = i; } } for (int i = 2; i < MAX_N; i++) { if(prime[i] == 0) prime[i] = i; } return ; }

int g_cnt[MAX_N + 5];

void init_g_cnt() { g_cnt[1] = 1;

  1. //将prime中等于0的项,初始化为下标,因为对于素数,其自身就是最小素数
  2. for (int i = 1; i <= MAX_N; i++) {
  3. if (prime[i] == 0) prime[i] = i;
  4. }
  5. //利用prime数组记录的最小素因子,计算每个数对应的素因子的个数
  6. for (int i = 2; i <= MAX_N; i++) {
  7. int n = i, cnt = 0, p = prime[i];
  8. while (n % p == 0) {
  9. cnt += 1;
  10. n /= p;
  11. }
  12. //在这里运用了”数论积性函数“的性质
  13. g_cnt[i] = g_cnt[n] * (cnt + 1);
  14. }
  15. return ;

}

//等比数列求和 int sum_euq(int m, int n) { int sum = 0; for (int i = 0; i <= n; i++) { sum += pow(m, i); } return sum; }

int g_sum[MAX_N + 5];

void init_g_sum() { g_sum[1] = 1; g_sum[2] = 3; g_sum[3] = 4;

  1. //利用prime数组记录的最小素因子,计算每个数对应的素因子的和
  2. for (int i = 3; i <= MAX_N; i++) {
  3. int n = i, cnt = 0, p = prime[i];
  4. while (n % p == 0) {
  5. cnt += 1;
  6. n /= p;
  7. }
  8. g_sum[i] = g_sum[n] * (sum_euq(p, cnt));
  9. }

}

int main() { init_prime(); int i; scanf(“%d”, &i); printf(“最小素数是:%d\n”, prime[i]); init_g_cnt(); printf(“素因数的个数是:%d\n”, g_cnt[i]);

  1. init_g_sum();
  2. printf("素因数的和是:%d\n", g_sum[i]);
  3. return 0;

}

  1. <a name="FFzff"></a>
  2. ###
  3. <a name="lhqBo"></a>
  4. ### 07
  5. <a name="j8xVJ"></a>
  6. #### 结构体占用的储存空间逻辑?
  7. ![image.png](https://cdn.nlark.com/yuque/0/2022/png/8419208/1649322383360-5cfa2f6c-5c12-4616-9d0d-11a7171ec1ed.png#clientId=uc5a5d3e1-2dbb-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=182&id=udabcdab1&margin=%5Bobject%20Object%5D&name=image.png&originHeight=311&originWidth=553&originalType=binary&ratio=1&rotation=0&showTitle=false&size=40028&status=done&style=none&taskId=uf7ecc39a-6f49-4c8b-bd1e-e8235d80e02&title=&width=324)
  8. - 结构体的“最小储存单元”大小,等同于其内部储存的最大的基础类型占用的空间大小;
  9. - 结构体内,数据的储存顺序,按照定义结构体时的顺序;如果当前“最小储存单元”不足以储存下一个基本数据类型,则从下一个储存单元的头部开始储存;
  10. 分析:
  11. - 两个结构体的最小储存单元都是其内部的最大基础类型:int,占4字节
  12. - `Data1`结构体中,先储存整型,再储存两个字节
  13. - 一个整型占一个“最小储存单元”,4字节
  14. - 后两个字符型,占两个字节;第二个“最小储存单元”够用
  15. - →所以,`Data1`的大小是8字节
  16. - `Data2`结构体中,先储存一个字符型,1字节,再储存一个整型,4字节,再储存一个字符型,1字节
  17. - 一个字符型占1个“最小储存单元”的1字节;
  18. - 但剩余的3个字节,不足以储存下一个数据类型:整型(4字节);
  19. - 因此,整型a储存在下一个“最小储存单元”中
  20. - 字符类型c储存在第三个“最小储存单元”中
  21. - →需要3个“最小储存单元”,即3×4=12字节
  22. <a name="HvK9b"></a>
  23. #### `&`与`*`是什么意思?
  24. - `&`可以取到变量的地址
  25. - `*`用来声明定义的变量是指针变量;也代表取指针内部所储存的值,也叫`**取值运算符**`;
  26. ```c
  27. int a = 45, *p = &a;
  28. // 此时:
  29. *p == a == 45;
  30. // 注意,两个*的区别,第一个用于声明定义的是一个指针变量,第二个是“取值运算符”

一个指针变量占用多少空间?

无论如何,指针变量都是一个指针,所以无论是什么类型的指针变量所占用的空间大小都是一样的;就像无论城市的量级有多不同,邮编都只占6位;

什么是间接引用与直接引用?

对于下面的结构体a[1].x是直接引用;a+1->x就是间接引用;
a是指向结构体变量a第一个元素,首个字段的首地址?
a+1是一个指针,指向变量a[1],那么a+1->x等价于a[1].x
前者是间接引用,后者是直接引用;

思考题:

  1. struct Data {
  2. int x, y;
  3. } a[2];
  4. struct Data *p = a;
  5. printf("%p", &a[1].x);

用尽可能多得形式,替换代码中&a[1].x 的部分,使得代码效果不变 :

  • 用间接引用实现
    • &((a+1)->x)
  • 巧妙使用指针类型
    • 思考结构体a在内存中的储存形式
    • a[1].x在内存中的前1位是什么?
    • a[0].y
    • 那么地址&a[1].x就等价于地址&(a[0].y)+1
    • 同理,也等价于&(a[1].y)-1
    • 也等价于&(a[2].x)-2
    • 也等价于&(a[2].y)-3

利用指针和a的等价:

  • 所有的p都可以用来替换a[0]
  • 所有的p+1都可以用来替换a[1],也可以等价于p[1]
  • 所有的p+2都可以用来替换a[2],也可以等价于p[2]

以上方式的思路都是通过a定位x的地址;
如何通过指针p定位x的地址呢?

  • *p = a指针p,指向的是a的头部,也就是a[0]
  • p + 1是哪?a[1]的头部
  • 那么&a[1].x等价于&(p + 1).x
    • 有错误,取地址运算符&的优先级低于直接引用运算符.,所以正确的写法应该是&((p + 1).x)

通过p,巧用指针类型:

  • ((p.y)+1)

    1. <br />

    本节课重点:

  1. 指针的类型,决定了通过指针取值时,向后取的字节数量;
  2. 指针的类型,决定了指针加减法时向前/后移动的字节的数量;
  3. 无论是任何类型的指针,大小都相等;

    06

    “字符串长度”与“字符串占用空间”

    "hello world"为例
  • 字符串长度是11
  • 字符串占用空间是12
  • 因为字符串结尾有一个结尾字符/0占用一个字节

思考题2:在不计算字符串长度的情况下,遍历输出字符串中的每一位;

不能使用if循环,不能使用计算字符串长度的方法
思路:

  • 使用while循环遍历
  • 通过判断字符串内容是否是"\0"来判断字符串是否结束
  • 还有很多问题没搞清楚有疑问
    • 单引号在c语言中的用法
    • str[i]到底是字符还是指针?
    • 如何判断字符串中某个位置的字符是否符合预期? ```python

      include

int main() { char str1[12] = “hello world”; int i = 0; while (str1[i] != ‘\0’) { // printf(“i = %d\n”, i); printf(“%c\n”, str1[i]); i++; } return 0; }

  1. <a name="i77d3"></a>
  2. #### 实现任意保留小数
  3. 思路:
  4. - 保留两位语句:`printf("%.2lf", a)`
  5. - 保留n位:`printf("%.nlf", a)`
  6. - printf的第一个参数其实就是一个字符串,实现根据输入的情况,自定义字符串中的n即可
  7. - `printf(str, a)``scanf("%d",&n)``sprintf(str, "%%.%dlf", n)`
  8. - sprint的功能是关键的一步,用n替代字符串中的内容,并把字符串赋值给字符数组
  9. ```python
  10. #include <stdio.h>
  11. int main() {
  12. int n;
  13. double num;
  14. char str[100];
  15. scanf("%lf%d", &num, &n);
  16. sprintf(str, "%%.%dlf\n", n);
  17. printf(str, num);
  18. return 0;
  19. }

05

字节、比特与地址

  • 比特是表示信息的最小单位
  • 字节数储存数据的基本单位
  • 地址是计算机中每个字节的唯一标识

    单一的&运算符放在变量前面,就是取变量的首地址

计算题

  1. #include <stdio.h>
  2. int f[1001];
  3. int main() {
  4. // 定义并读入m、n
  5. int n, m;
  6. printf("input n:");
  7. scanf("%d", &n);
  8. printf("input m:");
  9. scanf("%d", &m);
  10. // 逐个读入n个数
  11. int f1[n-1];
  12. for (int i = 0; i < n; i++){
  13. printf("input f[%d]:", i);
  14. scanf("%d", &f1[i]);
  15. }
  16. // 用于记录,每个元素都初始化为0
  17. int ans[m+1];
  18. for (int i = 0; i <= m + 1; i++) ans[i] = 0;
  19. // 把f1中的每个数的倍数作为下标对应的值都置为1
  20. for (int t = 0; t < n; t++) { // 针对每个i 逐个检查i能否被整除
  21. int n_max = m / f1[t];
  22. for (int i = 0; i < n_max + 1; i++) {
  23. ans[i * f1[t]] = 1;
  24. }
  25. }
  26. // 输出数组中,值仍为0的元素的下标
  27. for (int i = 0; i < m + 1; i++){
  28. if (ans[i] == 0) printf("%d ", i);
  29. }
  30. return 0;
  31. }

算法很重要!!!
image.png

04

真随机与伪随机

  • 真随机,每次取数,相互独立,上一个数不影响下一个数
  • 伪随机,实际上是一个伪随机序列,上一个数决定了下一个数;
  • 所有语言都是伪随机,程序通过第一个随机数和固定计算规则,给出剩下的随机数

    作业 打印乘法口诀表:

    ```c

    include

int main() { int i = 1, k = 1;

  1. for(k = 1; k < 7; k++) {
  2. for ( i = 1; i <=k; i++) {
  3. printf("%d*%d = %d", i, k, i*k);
  4. if(i != k) printf("\t"); else printf("\n");
  5. }
  6. }

return 0; }

  1. <a name="DmSwm"></a>
  2. #### 作业 完成日期计算器 done
  3. ```c
  4. #include <stdio.h>
  5. int main() {
  6. int y, m, d, x;
  7. printf("input y m d:\n");
  8. scanf("%d%d%d", &y, &m ,&d);
  9. printf("input how many days:\n");
  10. scanf("%d", &x);
  11. for (int i = 0; i < x; i++ ) {
  12. d += 1;
  13. printf("d plus done.\n");
  14. switch (m) {
  15. case 1: {
  16. if (d > 31) d = 1, m += 1;
  17. if (m == 13) m = 1, y += 1;
  18. }; break;
  19. case 2: {
  20. if((y % 4 == 0 && y % 100 != 0) || y % 400 == 0) {
  21. printf("你输入的是闰年\n");
  22. if (d > 28) {
  23. d = 1; m += 1;
  24. };
  25. if (m == 13) {
  26. m = 1; y += 1;
  27. };
  28. } else {
  29. printf("不是闰年\n");
  30. if (d > 27) d = 1; m += 1;
  31. if (m == 13) m = 1; y += 1;
  32. };
  33. }; break;
  34. default: {
  35. printf("default, 30days per month.\n");
  36. if (d > 30) d = 1, m += 1;
  37. if (m == 13) m = 1, y += 1;
  38. };
  39. case 12: {
  40. if (d > 31) d = 1, m += 1;
  41. if (m == 13) m = 1, y += 1;
  42. }; break;
  43. case 10: {
  44. if (d > 31) d = 1, m += 1;
  45. if (m == 13) m = 1, y += 1;
  46. }; break;
  47. case 8: {
  48. if (d > 31) d = 1, m += 1;
  49. if (m == 13) m = 1, y += 1;
  50. }; break;
  51. case 7: {
  52. if (d > 31) d = 1, m += 1;
  53. if (m == 13) m = 1, y += 1;
  54. }; break;
  55. case 5: {
  56. if (d > 31) d = 1, m += 1;
  57. if (m == 13) m = 1, y += 1;
  58. }; break;
  59. case 3: {
  60. if (d > 31) d = 1, m += 1;
  61. if (m == 13) m = 1, y += 1;
  62. }; break;
  63. };
  64. printf("%d年%d月%d日\n\n", y, m, d);
  65. };
  66. printf("final:%d年%d月%d日\n", y, m, d);
  67. return 0;
  68. }
  69. // 遇到的坑:if语句,接多个句子,不用大括号,句子之间错误的使用了;而没有使用,
  70. // 这导致,第一个;之后的语句 不再属于if结构体。
  71. // 基本功很重要,如果不是亲自写代码,不会发现自己有这个问题

为什么c用%d代表整型而不用%i

  • 实际上,用%i代表整型是可行的,c语言支持
  • 其次,%d中的d是decimal,十进制数,所以用%d不无道理

    printf如何格式化输出?自动右对齐,每个数字固定占3位那种未解决

    printf("%3d",a);打印宽度为3,右对齐,如果字数不足,则在左边补足空格;

    如何控制随机数的区间?

    rand()生成随机数
    rand() % ran_max得到[0-ran_max)区间的随机数
    1.0 * rand() / RAND_MAX得到[0.0,1.0]区间的随机数

    作业 用随机数计算π

    ```c

    include

    include

    include

int main() { int x; printf(“how many times:\n”); // 自定义进行实验的次数 scanf(“%d”, &x); int m = 0, n = 0; for (int i = 0; i < x; i++) { double x = 1.0 rand() / RAND_MAX; double y = 1.0 rand() / RAND_MAX; if (x x + y y <= 1) m += 1; n += 1; }; printf(“%lf\n”, 4.0 * m / n); return 0;

}

  1. <a name="cXdgg"></a>
  2. #### 搞清楚对象文件的作用:
  3. c语言中,程序从源代码到可执行文件,需要4个步骤:
  4. - 第一步,预处理源代码,生成“待编译源码”
  5. - 源代码就是我们自己写的代码;
  6. - 其中包含了一些“需要预先处理的命令”,比如`#include<stdio.h>`;
  7. - 预处理的作用,就是执行预处理命令;
  8. - 具体操作就是将需要的内容复制到源代码中,供下一个阶段使用;
  9. - 第二步,编译“待编译源码”,生成“对象文件”
  10. - 待编译源码是完全体的代码,但仍旧是一种“人类创造出来的语言”(c语言)
  11. - 但计算机看不懂,计算机只能看懂0和1
  12. - 所以需要讲人类的语言“翻译成”计算机能看懂的语言
  13. - 这个步骤就是编译
  14. - 但是编译后,还有些对象需要链接?未解决
  15. - 即对象文件是“已编译但未链接“
  16. - 第三步,链接对象文件,生成可执行程序
  17. - 比如需要调用`printf`函数,`printf`函数存在于预先编译好的printf.o文件中,链接器负责将printf和调用printf的代码合并,得到可执行程序
  18. <a name="SK5yi"></a>
  19. #### 如何在macOS终端运行c程序?
  20. - 需要安装xcode
  21. - 创建hello.c文件
  22. - 通过终端编译文件`gcc hello.c`
  23. - 编译通过,会在当前目录生成`a.out`文件
  24. - 通过终端执行`a.out`文件`./a.out`
  25. - 如果是多个c语言源文件,则需要通过`gcc file1.c file2.c -o main.out`生成名为main.out的文件,再通过`./main.out`执行
  26. <a name="Sem90"></a>
  27. #### 编译+执行太麻烦了 如何用vim直接执行?
  28. 好像不可以... 用IDE比较方便
  29. <a name="iJuAZ"></a>
  30. #### c语言中的&和*分别是什么意思?未解决
  31. `*`代表指针、或取值(访问指针)<br />`&`代表取地址?<br />`int *p`<br />`printf("%d", p)`——用`%d`来输出地址
  32. <a name="FQwg7"></a>
  33. #### 关于指针:
  34. - 定义一个指针变量`int *p`
  35. - 把变量地址赋值给指针
  36. ```c
  37. int a = 100;
  38. int *p;
  39. p = &a; //把变量的地址赋值给指针
  • 访问指针变量中可用地址的值

    1. int *p;
    2. printf("%d", *p); /访问指针变量中可用地址的值
  • 指针的类型可以进行运算++ -- + -

    • 指针的+运算,取决于指针的类型;
    • 假设ptr指向的地址是1000
    • 若指针ptr为整型int *ptr,则ptr++指向的地址是1004
    • 若指针的类型为字符型,即str *ptr,则ptr++指向的地址是1001 ```c

      include

const int MAX = 3;

int main() { int var[] = {1, 2, 3}; int i, *ptr;

  1. ptr = var;
  2. for (i = 0; i < MAX; i++) {
  3. printf("var[%d]的地址是:%p\n", i, ptr);
  4. printf("var[%d]的值是:%d\n", i, *ptr );
  5. ptr++;
  6. }
  7. return 0;

}

  1. - 指针也可以用来比较`== < >`
  2. - 当两个指针`p1 p2`指向了两个相关的变量,比如同一个数组中的两个不同元素,则可以对两个指针进行大小的比较
  3. ```c
  4. #include <stdio.h>
  5. const int MAX = 3;
  6. int main() {
  7. int var[] = {1, 2, 3};
  8. int i, *ptr;
  9. ptr = var;
  10. i = 0;
  11. while(ptr <= &var[MAX - 1]) {
  12. printf("var[%d]的地址是:%p\n", i, ptr);
  13. printf("var[%d]的值是:%d\n", i, *ptr );
  14. ptr++;
  15. i++;
  16. }
  17. return 0;
  18. }
  • 传递指针给函数 并在函数内部修改指针的值 ```c

    include

    include

void getSeconds(unsigned long *par);

int main() { unsigned long sec;

  1. getSeconds( &sec );
  2. printf("number of seconds:%ld\n", sec);
  3. return 0;

}

void getSeconds(unsigned long par) { par = time(NULL); return; }

  1. <a name="Vv3YK"></a>
  2. #### c中的“宏”是什么?未解决
  3. 查找替换<br />`va_start(va_list argptr, lastparam);`<br />`va_start`是一个宏
  4. <a name="Pe0EP"></a>
  5. #### c如何注释?
  6. - 行内注释`//`
  7. - 块注释`/* ... */`
  8. <a name="Fvd1L"></a>
  9. #### 如何一次性声明多个头文件?
  10. <a name="LhC9M"></a>
  11. #### 为什么c程序里一定要写main函数
  12. <a name="QyxT0"></a>
  13. #### 学习可选参数的实现:
  14. 参考链接:[http://c.biancheng.net/view/344.html](http://c.biancheng.net/view/344.html)
  15. ```c
  16. // 函数add() 计算可选参数之和
  17. // 参数:第一个强制参数指定了可选参数的数量,可选参数为double类型
  18. // 返回值:和值,double类型
  19. #include <stdarg.h>
  20. #include <stdio.h>
  21. double add(int n, ... )
  22. {
  23. int i = 0;
  24. double sum = 0.0;
  25. va_list argptr;
  26. va_start( argptr, n ); // 初始化argptr
  27. for ( i = 0; i < n; ++i ) // 对每个可选参数,读取类型为double的参数,
  28. sum += va_arg( argptr, double ); // 然后累加到sum中
  29. va_end( argptr );
  30. return sum;
  31. }
  32. int main(){
  33. printf("%lf", add(4,1.1,2.2,2.3));
  34. }

break与continue

  • break,跳出整个循环
  • continue,跳出本次循环

    转义字符

    \b退格;实测会删掉\b前面的一个字符
    \033设置颜色

    关于scanf()函数

  • 读取int、float、char等类型在用于scanf函数的时候,都需要在前面加&

  • 因为scanf需要的是变量的地址
  • 而scanf在接受数组和字符串的时候,不需要在前面加&
  • 因为数组和字符串的名称在使用时会自动转换为首个元素的地址

    %c和%s的区别

    %c读入一个字符
    %s读入一个字符串,遇到空格、换行符或制表符结束