原课程地址: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'
就不会..待进一步搜索:
- 含义不同;单引号引起的字符,实际上代表一个整数,该整数对应于字符在对应字符集中的序列值;比如常用的ASCII字符集,
's'
的含义其实和十进制数115
是相同的;而双引号引起的字符串,代表的是一个指向无名数组起始字符的指针; - 大小不同;单引号引起的字符,大小就是1个字节,双引号引起的字符串,大小是字符个数+1个字节,因为会自动在字符串末尾添加二进制值为0的字符
'\0'
;试验
printf("%d", 'b' - 'a');
输出的结果是1,即两个字符相减的结果是一个整型;getchar
的用法、和scanf的区别
以无符号char强制转换为int的形式返回读取的字符
即将一个字符强制转换为int形式putchar
则与其相反,以无符号 char 强制转换为 int 的形式返回写入的字符,如果发生错误则返回 EOF。
scanf
遇到回车
停止读取,但不接收回车
,此时回车
在输入缓冲区中;getchar()
从缓冲区读走一个字符;
scanf()
的返回值
如果成功,返回成功匹配和赋值的个数,如果出现错误或遇到文件末尾,则返回EOF;
scanf()的%[]用法
%[]
叫做扫描集,用于给字符串或字符数组赋值;scanf函数
以空白字符空格符、制表符或回车符
结束对变量的赋值;- 所有变量都已赋值的情况下,回车是对scanf函数的结束,即继续执行下面的语句;
这就引出来问题:
- 如果想把空白字符赋值给变量怎么办?
- 如果不想用空白字符作为结束,能否用自定的其他字符作为赋值的结束?
此时,%[]
扫描集就能解决以上问题:
int main() { char str[100]; printf(“input str:”); scanf(“%[abc]”, str); printf(“str = %s”, str); return 0;
}
输出:abcdef 输出:str = abc
2. `%[^string]`只读取非string字符(^有取反的作用),换言之,只要出现了string中的字符,就停止赋值;
```python
#include <stdio.h>
int main() {
char str[100];
printf("input str:");
scanf("%[^abc]", str);
printf("str = %s", str);
return 0;
}
===
输入:12a34
输出: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|1
和code(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编码,供我们在单模匹配中使用;
编码的形式,我们看到的是十进制形式,但其二进制表示就是我们想要的内容;??有疑问
// str是字符串,p_str是模式串
int shift_and(const char *str, const char *p_str) {
int code[256] = {0}, m = 0; // code是一个长度为256的数组,并且每一位都初始化为0;为什么是256?,m用于记录模式串的长度
// 初始化每一个字符的编码,怎么编的??不懂;编码方式描述:模式串第i个字符的编码,等于“1左移i位”与“上一个字符的编码”进行“逻辑或”运算
for (int i = 0; p_str[i]; i++, m++) {
code[p_str[i]] |= (1 << i);
}
int p = 0;
for (int i = 0; str[i]; i++) {
p = (p << 1 | 1) & code[str[i]];
// 如果 p 所对应的模式串最高位为1,代表匹配成功
if (p & (1 << (m - 1))) return 1;
}
return 0;
}
按位或、按位与、按位异或:
将位对齐,位与位之间进行计算
- 按位或:两者其中一个是
1
,按位或的结果就是1
,否则是0
- 按位与:只有两者同时是
1
,按位与的结果才是1
,否则是0
- 按位异或:两者不一样,按位异或的结果就是
1
,否则是0
课程笔记
30 自己编写简易计算器
二叉树的三种遍历方式
- 前序遍历:根节点 { 前序遍历左子树 } { 前序遍历右子树 }
- 中序遍历:{ 中序遍历左子树 } 根节点 { 中序遍历右子树 }
- 后序遍历:{ 后序遍历左子树 } { 后序遍历右子树 } 根节点
仔细观察上面的遍历定义,是一种“递归”定义方式;即{ 前序遍历左子树 }中,把左子树的子节点看做根节点,继续遵循前序遍历的原则进行遍历;
表达式树与后序遍历
任何一个四则运算的表达式,都可以表现为一个二叉树
而表达式计算的值,就是对应的二叉树**后序遍历**
的结果
用后序遍历的思路去做递归,代码体现就是:
// 假设函数get_val可以计算字符串表达式String的结果
// op代表string表达式中,最后一个计算的运算符
// 对于任1字符串表达式,都可以写成下述形式:
get_val(String) = get_val(L_String) op get_val(R_String)
如何确定运算的顺序?
针对上面的计算逻辑:“确定当前表达式中,最后一个计算的运算符”就显得尤为重要
如何确定运算符之间的优先级?——通过给所有的运算符附上“计算权重”
对于括号对运算符计算顺序的改变,可以“遇到左括号”就“给运算符加上一个很大的权重”,相反,“遇到右括号”就给运算符减去相同数量的权重
同时,设置一个标签,用于记录当前最低运算优先级的位置;(需在该位置将字符串切分为两个字符串)
#include <stdio.h>
#include <string.h>
#define INF 0x3f3f3f3f
// 计算表达式的值
int calc(const char* String, int l, int r) {
// 查找当前表达式的根节点
// pos记录当前根节点的位置,初始化为-1
// priority 记录根节点的权重等级
// temp 记录括号带来的权重增加
int pos = -1, priority = INF - 1, temp = 0;
for (int i = l; i <= r; i++) {
int cur_priority = INF;
switch(String[i]) {
case '(' : temp += 100; break;
case ')' : temp -= 100; break;
case '+' :
case '-' : cur_priority = 1 + temp; break;
case '*' :
case '/' : cur_priority = 4 + temp; break;
default : break;
}
// 判断当前运算符的优先级
// 如果其小于根节点的运算符,就将其设置为新的根节点
if (cur_priority <= priority) {
pos = i;
priority = cur_priority;
// printf("update priority: %c, %d, priority is %d\n", String[pos], pos, priority);
}
}
// 如果当前根节点就是优先级最低的根节点,说明当前表达式中没有运算符?则计算
if (pos == -1) {
int sum = 0;
for (int i = l; i <= r; i++) {
// 判断 如果有空格之类的,直接跳过
if (String[i] < '0' || String[i] > '9') continue;
sum = sum * 10 + (String[i] - '0');
}
// printf("num = %d\n", sum);
return sum;
}
int a = calc(String, l, pos - 1);
int b = calc(String, pos + 1, r);
switch(String[pos]) {
case '+' : printf("%d + %d = %d\n\n", a, b, a + b); return a + b;
case '-' : printf("%d - %d = %d\n\n", a, b, a - b); return a - b;
case '*' : printf("%d * %d = %d\n\n", a, b, a * b); return a * b;
case '/' : printf("%d / %d = %d\n\n", a, b, a / b); return a / b;
}
return 0;
}
int get_value(const char *str) {
return calc(str, 0, strlen(str) - 1);
}
int main() {
char str1[100];
char a[] = "2+(1+3)*2";
printf("%s = %d\n", a, get_value(a));
while (scanf("%[^\n]", str1) != EOF) { // 只读取非回车字符
getchar(); //getchar在此处的作用是什么?猜测是清空掉空格
//测试了一下,如果不写getchar,回车后,
//程序会不断循环计算第一次输入的公式
//但,具体是如何实现的呢?
//分析:getchar读走了scanf停止读入时,留在输入缓存区里的\n
//如果没有getchar,缓存区中一直有内容
//scanf的结果就一直!=EOF,就陷入了死循环
printf("%s = %d\n", str1, get_value(str1));
}
return 0;
}
如何实现定义变量?未解决
只需要将26个小写字母作为变量,可以定义一个结构体?
利用小写字符的ascii码,将其与数组的i对应起来?(a的ASCII码是97)
定义一个结构体,用于储存定义的变量和对应的值
结构体内有两个成员
:
- 字符型的
名称
- 整型的
值
尝试使用结构体:
#include <stdio.h>
struct new_number //定义结构体
{
char name; // 结构体成员
int value; // 结构体成员
};
int main() {
struct new_number a = {'a', 1}; // 结构体实例初始化
printf("a.name = %c, a.value = %d", a.name, a.value); // 结构体成员的访问
return 0;
}
将结构体融入之前的代码:
01 先尝试无输入,直接在程序内部初始化结构体:
需要有一个“把变量替换成数值”的过程——遍历表达式中的每个字符,如果是26个字母中的一个,就替换成对应的数值
问题:如何根据结构体的某个成员,访问结构体的另一个成员?(有点像对字典中内容的访问)?
感觉实现起来有些麻烦,决定再去看看关于“链表”的内容
(其中的7.2讲述的是简单链表简单链表)
为什么要尝试链表?可以动态管理,在计算器中,不确定用户想要输入几个变量;
简单链表的尝试
#include <stdio.h>
struct student {
long number;
float score;
struct student *next;
};
int main () {
struct student a, b, c, *head, *p;
a.number = 10001, a.score = 99.8;
b.number = 10002, b.score = 98.1;
a.next = &b, b.next = &c, c.next = NULL;
head = &a;
p = head;
while(p != NULL) {
printf("number: %ld\nscore: %.1f\n", p->number, p->score);
p = p->next;
}
return 0;
}
如何实现链表的动态管理?未解决
scanf的类型说明符``%[]
char const *str
scanf("%[]", str)
%[]
在scanf中用于扫描字符集合scanf("%[^\n]", str)
语句的作用是碰见了回车就把缓冲区的内容按字符串格式存入到str指向的地址中,但此时回车还留在缓冲区内;ps.^
是异或运算符,不同则结果为1,相同则结果为0;
29
28
27 测试框架
26 算法篇小结
25动态规划(下)
多重背包
解析:(c++版本)
#include <stdio.h>
#define N 100
#define max(a, b) (a) > (b) ? (a) : (b)
int m, C, dp[N][N]; // 分别是数量、背包容量和能拿的最大价值
int w[N] = {0, 4, 3, 12, 9}, n[N] = {0, 5, 4, 2, 7}, v[N] = {0, 10, 7, 12, 8}; // 分别是重量、数量、价值
int new_m;
int new_w[N], new_n[N], new_v[N];
// 写在函数里,发现不行...但写在主程序里的却可以
int get_dp(int n, int W, int * n_w, int * n_v) {
// 初始化 dp[0] 阶段
for (int i = 0; i <= W; i++) dp[0][i] = 0;
// 假设 dp[i - 1] 成立,计算得到 dp[i]
// 状态转移过程,i 代表物品,j 代表背包限重
for (int i = 1; i <= n; i++) {
for (int j = 0; j <= W; j++) {
// 不选择第 i 种物品时的最大值
dp[i][j] = dp[i - 1][j];
// 与选择第 i 种物品的最大值作比较,并更新
if (j >= n_w[i] && dp[i][j] < dp[i - 1][j - n_w[i]] + n_v[i]) {
dp[i][j] = dp[i - 1][j - n_w[i]] + n_v[i];
}
}
}
return dp[n][W];
}
int main() {
m = 18;
// 二进制拆分
new_m = 0;
for (int i = 1; i < m; i++) {
for (int j = 1; j <= n[i]; j <<= 1) {
n[i] -= j; // 减去拆掉的内容
new_w[++new_m] = w[i] * j; // 新重量
new_v[new_m] = v[i] * j; // 新价值
}
if (n[i]) { // 余下的是余数,计算余数的重量和价值
new_w[++new_m] = w[i] * n[i];
new_v[new_m] = v[i] * n[i];
}
}
// 打印二进制分配后的物品
for (int i = 0; i < 20; i++) printf("%3d %3d\n", new_v[i], new_w[i]);
printf("new_m = %d\n", new_m);
// 0/1背包
// int ans = get_dp(new_m, 30, new_w, new_n);
// printf("ans = %d\n", ans);
C = 15;
// 初始化物品为0的阶段
for (int i = 0; i <= C; i++) dp[0][i] = 0;
// 假设dp[i - 1]成立,计算dp[i]
for (int i = 0; i <= new_m; i++) {
for (int j = 1; j <= C; j++ ) {
dp[i][j] = dp[i - 1][j];
if (j >= new_w[i] && dp[i][j] < dp[i - 1][j - new_w[i]] + new_v[i]) {
dp[i][j] = dp[i - 1][j - new_w[i]] + new_v[i];
}
}
}
printf("%d", dp[new_m][C]);
return 0;
24 动态规划(上)
动态规划问题的四步骤:
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
<a name="NC0mA"></a>
### 23 容拆原理与递推算法
如果想更深入地了解一下容斥原理,可以通过学习莫比乌斯函数、狄利克雷卷积与莫比乌斯反演等内容,进一步感受一下这个思想所绽放出的光芒。
```c
// 凑钱
#include <stdio.h>
int val[8] = {0, 1, 2, 5, 10, 20, 50, 100};
int find_combs (int i, int j) {
if (j == 0 || i > j) return 1; // 为什么j=0的时候 要return 1;呢?
if (i == 1 && (j % val[1] == 0)) return 1;
return find_combs(i - 1, j) + find_combs(i, j - val[i]);
}
int main () {
int a = find_combs(3, 1);
printf("%d\n", a);
printf("%d\n", val[3]);
return 0;
}
22 单调栈、木板切割
括号有效性 leetcode关于此题的讲解:leetcode
#include <stdio.h>
#include <string.h>
//读入有括号,返回对应的左括号,如果读入的是左括号,就返回0
char pair(char a) {
if (a == ']') return '[';
if (a == '}') return '{';
if (a == '>') return '<';
if (a == ')') return '(';
return 0;
}
//利用栈,检查是否匹配;
//先将左括号依次填入栈
//对于每个右括号,返回其匹配的左括号,如果此时栈内不为空,且栈顶的左括号与返回的左括号相同,则匹配成功
//检查下一个栈顶;
int isValid(char *s) {
int n = strlen(s);
char stk[n + 1], top = 0;
for (int i = 0; i < n; i++) {
char ch = pair(s[i]);
if (ch) {
if(top == 0 || stk[top - 1] != ch) return 0;
top--;
} else {
stk[top++] = s[i];
}
}
return 1;
}
int main() {
char s[4] = "<(>";
int ans = isValid(s);
printf("%d", ans);
}
太难了 示例代码都看不懂未解决
姑且看懂了一些
ps条件表达式? :
bool ?
a :
b
条件bool是否成立?如果成立,则值为a,否则为b
#define MAX_N 1000
#define max(a, b) ((a) > (b) ? (a) : (b))
int s[MAX_N + 5], top;
int l[MAX_N + 5], r[MAX_N + 5];
int max_matrix_area(int *h, int n) {
h[0] = h[n + 1] = -1;
top = -1, s[++top] = 0;
// 找到每一块木板,左边第一块比其矮的木板编号
// 如果栈顶指向的木板比i长,就看栈里下一块木板(从底至顶单调递增栈)
// 直到找到第一块比i短的木板,记录在l[i]中,就是左侧第一块比其矮的木板
// 并通过s[++top] = i将其记录在栈顶
// 疑问:单调递增栈是何时形成的?-是通过s[++top] = i形成的
// 单独看while循环和s[++top] = i,意思是:从顶到底遍历栈,当遍历项长度小于当前i长度时,将i放在该项之上(就是形成递增栈的过程)
for (int i = 1; i <= n; i++) {
while (top >= 0 && h[s[top]] >= h[i]) --top;
l[i] = s[top];
s[++top] = i;
}
// 找到每一块木板,右边第一块比其矮的木板编号
top = -1, s[++top] = n + 1;
for (int i = n; i >= 1; i--) {
while (top >= 0 && h[s[top]] >= h[i]) --top;
r[i] = s[top];
s[++top] = i;
}
// 在所有木板中,找到面积最大的矩形
int ans = 0;
for (int i = 1; i <= n; i++) {
ans = max(ans, (r[i] - l[r] - 1) * h[i]);
}
return ans;
}
21 单调队列 滑动区间最大值
#include <stdio.h>
#define MAX_N 1000
int q[MAX_N + 5], head, tail;
//a指向原始队列,m是单调队列长度,n是原始队列总个数
void invertal_max_number(int *a, int n, int m) {
head = tail = 0;
for (int i = 0; i < n; i++) {
//形成初始队列,将不符合单调性的都踢出去
while (head < tail && a[q[tail - 1]] < a[i]) tail--;
//直到维护了单调性,i可以入队了
q[tail++] = i;
//判断队列长度是否超出了范围
if(i - m == q[head]) head++;
//输出区间内的最大值,i+1大于区间长度,确保第一个区间已经形成
if(i + 1 >= m) {
printf("invert(%d, %d)=", i - m + 1, i);
printf("%d\n", a[q[head]]);
printf("q[head]=%d, q[tail]=%d\n\n", q[head], q[tail]);
}
}
return ;
}
int main() {
int a[8] = {1, 3, -1, -3, 5, 3, 6, 7};
invertal_max_number(a, 8, 3);
return 0;
}
20 二分查找
二进制右移>>
通常可以理解为除以2
(大部分情况下成立)
练习:通过税后工资 反推税前工资
思路:读入工资-二分法查找所在区间-反推税前工资-
#include <stdio.h>
//计算税后工资的函数
double salary_aft(double salary) {
if (salary <= 3000) return salary * 0.97;
if (salary <= 12000) return (salary - 3000) * 0.9 + 3000 * 0.97;
if (salary <= 25000) return (salary - 12000) * 0.8 + 3000 * 0.97 + 9000 * 0.9;
if (salary <= 35000) return (salary - 25000) * 0.75 + 3000 * 0.97 + 9000 * 0.9 + 13000 * 0.8;
return -1;
}
//二分查找
double binary_search(double x, double l, double r) {
double mid;
while (l < r) {
mid = (l + r) / 2.0;
if (salary_aft(mid) == x) return mid;
if (salary_aft(mid) < x) l = mid;
r = mid;
}
return -1.0;
}
int main () {
double y;
y = binary_search(23000.0, 0.0, 35000.0);
printf("%.2f\n", y);
return 0;
}
分析:永远返回-1,是因为无论循环多少次,结束循环的时候都会return -1;
解决办法:改成return mid;
当循环结束的时候,l == r == mid
,也就是我们要找的值,直接返回mid
即可;
可运行的代码如下:
bug1:输入小于3000的数字,会让程序陷入无线循环;
bug2:输入最后一个梯度的数字,计算是错误的,如下图:
分析原因,无限循环和 segmentation fault
错误,是因为double的精度有限,不可能无限的循环下去,因此需要以double类型的精度为基准,为循环设置条件——一般我们认为浮点数的误差在10的负七次方
以下的时候,就认为两个数字相等,因此将while循环的条件做更改:
由while(r < l)
改为while(r - l >= 1e-7)
并且在起始位置增加条件if(r - l <= 1e-7) return r;
最终可运行的代码如下:
#include <stdio.h>
//计算税后工资的函数
double salary_aft(double salary) {
if (salary <= 3000) return salary * 0.97;
if (salary <= 12000) return (salary - 3000) * 0.9 + 3000 * 0.97;
if (salary <= 25000) return (salary - 12000) * 0.8 + 3000 * 0.97 + 9000 * 0.9;
if (salary <= 35000) return (salary - 25000) * 0.75 + 3000 * 0.97 + 9000 * 0.9 + 13000 * 0.8;
return -1;
}
//二分查找
double binary_search(double x, double l, double r) {
double mid;
if (r - l <= 1e-7) return r;
while (r - l >= 1e-7) {
mid = (l + r) / 2.0;
if (salary_aft(mid) == x) return mid;
if (salary_aft(mid) < x) l = mid;
if (salary_aft(mid) > x) r = mid;
}
return mid;
}
int main () {
double x, y;
printf("请输入税后工资:");
scanf("%lf", &x);
y = binary_search(x, 0.0, 35000.0);
printf("税前工资是:%.2f\n", y);
printf("验证计算税后工资:\n");
printf("%.2f", salary_aft(y));
return 0;
}
#include <stdio.h>
//计算税后工资的函数
double salary_aft(double salary) {
if (salary <= 3000) return salary * 0.97;
if (salary <= 12000) return (salary - 3000) * 0.9 + 3000 * 0.97;
if (salary <= 25000) return (salary - 12000) * 0.8 + 3000 * 0.97 + 9000 * 0.9;
if (salary <= 35000) return (salary - 25000) * 0.75 + 3000 * 0.97 + 9000 * 0.9 + 13000 * 0.8;
return -1;
}
//二分查找,递归思维
double binary_search(double x, double l, double r) {
double mid = (l + r) / 2.0;
if (r - l <= 1e-7) return l;
if (salary_aft(mid) == x) return mid;
if (salary_aft(mid) < x) return binary_search(x, mid, r);
if (salary_aft(mid) > x) return binary_search(x, l, mid);
return -1.0;
}
int main () {
double x, y;
printf("请输入税后工资:");
scanf("%lf", &x);
y = binary_search(x, 0.0, 35000.0);
printf("税前工资是:%.2f\n", y);
printf("验证计算税后工资:\n");
printf("%.2f", salary_aft(y));
return 0;
}
二分法切绳子
#include <stdio.h>
#include <math.h>
//初始化记录,原始的绳子长度和数量
double l[100], n;
//切绳子函数,接收参数:想要的绳子长度x,返回参数:能切出的最多个数y
int f(double x) {
int m = 0;
for(int i =0; i < n - 1; i++) {
m += (int)floor(l[i] / x);
}
return m;
}
//二分法查找
double binary_search(double l, double r, int k) {
if (r - l <= 1e-7) return r;
double mid = (l + r) / 2.0;
if (f(mid) < k) return binary_search(l, mid, k);
return binary_search(mid, r, k);
}
int main() {
double l[3] = {4.0, 5.0, 6.0};
int n = 3;
double m;
m = binary_search(l[0], l[2], 4);
printf("%f\n", m);
}
19 运用链表思维解决快乐数问题
18 链表
链表的插入,为什么我理解的总是错位?
struct Node *insert(struct Node *head, int ind, struct Node *a) {
struct Node ret, *p = &ret;
ret.next = head;
// 从【虚拟头节点】开始向后走 ind 步
while (ind--) p = p->next; //这里用的ind--,而不是--ind;即先使用ind的值,再进行--
// 完成节点的插入操作
a->next = p->next;
p->next = a;
// 返回真正的链表头节点地址
return ret.next;
}
再仔细看,不是删除了两个,而是比我写的向后删除了一个
我的代码是p = p->next;
有疑问
#include <stdio.h>
//定义链表节点结构
struct Node {
int data;
struct Node *next;
};
//定义链表的插入方法
struct Node *insert(struct *head, int ind, struct Node *a) {
struct Node ret, *p = &ret;
ret.next = head;
//从虚拟头结点向后走ind步
while(ind--) p = p->next;//此处ind还是越来ind的值,先用后减
//完成节点插入的操作
a->next = p->next;
p->next = a;
//返回链表头结点的位置
return ret.next;
}
//定义链表的删除方法
struct Node *erase(struct Node *head, int ind) {
struct Node ret, *p = &ret;
ret.next = head;
//思路:向后走ind步,完成节点的删除操作
while(ind--) p->next;
p = p->next;
return ret.next;
}
17
16
实现大整数的相加
函数没能改变main函数中定义的数组a[]
之前明明实现了,是因为我把函数从void
改成了int
吗?不是
好像是因为在函数中又定义了一个同名数组a[]
;是的
现在把a[]
的定义放在所有代码的最前面,并且不在函数中定义同名的数组,函数所更改的就是最前面定义的a[]
;
如何实现在main中定义,在函数中修改、或者 不需要定义,直接在函数中返回呢?未解决
c语言不允许返回数组作为函数的参数,但是通过指定不带索引的数组名来返回一个指向数组的指针,可以通过该指针访问数组
strlen
-
c语言 字符和数字的相互转换:
int类型加‘0’变字符
char类型减‘0’变int
char b = '5';
printf("%d", b - '0');
https://blog.csdn.net/qq742143797/article/details/107450976
如何不用len函数”计算数组的长度“或”遍历数组“?未解决
-
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;
//将prime中等于0的项,初始化为下标,因为对于素数,其自身就是最小素数
for (int i = 1; i <= MAX_N; i++) {
if (prime[i] == 0) prime[i] = i;
}
//利用prime数组记录的最小素因子,计算每个数对应的素因子的个数
for (int i = 2; i <= MAX_N; i++) {
int n = i, cnt = 0, p = prime[i];
while (n % p == 0) {
cnt += 1;
n /= p;
}
//在这里运用了”数论积性函数“的性质
g_cnt[i] = g_cnt[n] * (cnt + 1);
}
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;
//利用prime数组记录的最小素因子,计算每个数对应的素因子的和
for (int i = 3; i <= MAX_N; i++) {
int n = i, cnt = 0, p = prime[i];
while (n % p == 0) {
cnt += 1;
n /= p;
}
g_sum[i] = g_sum[n] * (sum_euq(p, cnt));
}
}
int main() { init_prime(); int i; scanf(“%d”, &i); printf(“最小素数是:%d\n”, prime[i]); init_g_cnt(); printf(“素因数的个数是:%d\n”, g_cnt[i]);
init_g_sum();
printf("素因数的和是:%d\n", g_sum[i]);
return 0;
}
<a name="FFzff"></a>
###
<a name="lhqBo"></a>
### 07
<a name="j8xVJ"></a>
#### 结构体占用的储存空间逻辑?
![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)
- 结构体的“最小储存单元”大小,等同于其内部储存的最大的基础类型占用的空间大小;
- 结构体内,数据的储存顺序,按照定义结构体时的顺序;如果当前“最小储存单元”不足以储存下一个基本数据类型,则从下一个储存单元的头部开始储存;
分析:
- 两个结构体的最小储存单元都是其内部的最大基础类型:int,占4字节
- `Data1`结构体中,先储存整型,再储存两个字节
- 一个整型占一个“最小储存单元”,4字节
- 后两个字符型,占两个字节;第二个“最小储存单元”够用
- →所以,`Data1`的大小是8字节
- `Data2`结构体中,先储存一个字符型,1字节,再储存一个整型,4字节,再储存一个字符型,1字节
- 一个字符型占1个“最小储存单元”的1字节;
- 但剩余的3个字节,不足以储存下一个数据类型:整型(4字节);
- 因此,整型a储存在下一个“最小储存单元”中
- 字符类型c储存在第三个“最小储存单元”中
- →需要3个“最小储存单元”,即3×4=12字节
<a name="HvK9b"></a>
#### `&`与`*`是什么意思?
- `&`可以取到变量的地址
- `*`用来声明定义的变量是指针变量;也代表取指针内部所储存的值,也叫`**取值运算符**`;
```c
int a = 45, *p = &a;
// 此时:
*p == a == 45;
// 注意,两个*的区别,第一个用于声明定义的是一个指针变量,第二个是“取值运算符”
一个指针变量占用多少空间?
无论如何,指针变量都是一个指针,所以无论是什么类型的指针变量所占用的空间大小都是一样的;就像无论城市的量级有多不同,邮编都只占6位;
什么是间接引用与直接引用?
对于下面的结构体a[1].x
是直接引用;a+1->x
就是间接引用;
a是指向结构体变量a第一个元素,首个字段的首地址?
a+1是一个指针,指向变量a[1],那么a+1->x
等价于a[1].x
前者是间接引用,后者是直接引用;
思考题:
struct Data {
int x, y;
} a[2];
struct Data *p = a;
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,巧用指针类型:
- 指针的类型,决定了通过指针取值时,向后取的字节数量;
- 指针的类型,决定了指针加减法时向前/后移动的字节的数量;
- 无论是任何类型的指针,大小都相等;
06
“字符串长度”与“字符串占用空间”
"hello world"
为例
- 字符串长度是11
- 字符串占用空间是12
- 因为字符串结尾有一个结尾字符
/0
占用一个字节
思考题2:在不计算字符串长度的情况下,遍历输出字符串中的每一位;
不能使用if循环,不能使用计算字符串长度的方法
思路:
- 使用while循环遍历
- 通过判断字符串内容是否是
"\0"
来判断字符串是否结束 - 还有很多问题没搞清楚有疑问
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; }
<a name="i77d3"></a>
#### 实现任意保留小数
思路:
- 保留两位语句:`printf("%.2lf", a)`
- 保留n位:`printf("%.nlf", a)`
- printf的第一个参数其实就是一个字符串,实现根据输入的情况,自定义字符串中的n即可
- `printf(str, a)``scanf("%d",&n)``sprintf(str, "%%.%dlf", n)`
- sprint的功能是关键的一步,用n替代字符串中的内容,并把字符串赋值给字符数组
```python
#include <stdio.h>
int main() {
int n;
double num;
char str[100];
scanf("%lf%d", &num, &n);
sprintf(str, "%%.%dlf\n", n);
printf(str, num);
return 0;
}
05
字节、比特与地址
计算题
#include <stdio.h>
int f[1001];
int main() {
// 定义并读入m、n
int n, m;
printf("input n:");
scanf("%d", &n);
printf("input m:");
scanf("%d", &m);
// 逐个读入n个数
int f1[n-1];
for (int i = 0; i < n; i++){
printf("input f[%d]:", i);
scanf("%d", &f1[i]);
}
// 用于记录,每个元素都初始化为0
int ans[m+1];
for (int i = 0; i <= m + 1; i++) ans[i] = 0;
// 把f1中的每个数的倍数作为下标对应的值都置为1
for (int t = 0; t < n; t++) { // 针对每个i 逐个检查i能否被整除
int n_max = m / f1[t];
for (int i = 0; i < n_max + 1; i++) {
ans[i * f1[t]] = 1;
}
}
// 输出数组中,值仍为0的元素的下标
for (int i = 0; i < m + 1; i++){
if (ans[i] == 0) printf("%d ", i);
}
return 0;
}
04
真随机与伪随机
- 真随机,每次取数,相互独立,上一个数不影响下一个数
- 伪随机,实际上是一个伪随机序列,上一个数决定了下一个数;
- 所有语言都是伪随机
?
,程序通过第一个随机数和固定计算规则,给出剩下的随机数作业 打印乘法口诀表:
```cinclude
int main() { int i = 1, k = 1;
for(k = 1; k < 7; k++) {
for ( i = 1; i <=k; i++) {
printf("%d*%d = %d", i, k, i*k);
if(i != k) printf("\t"); else printf("\n");
}
}
return 0; }
<a name="DmSwm"></a>
#### 作业 完成日期计算器 done
```c
#include <stdio.h>
int main() {
int y, m, d, x;
printf("input y m d:\n");
scanf("%d%d%d", &y, &m ,&d);
printf("input how many days:\n");
scanf("%d", &x);
for (int i = 0; i < x; i++ ) {
d += 1;
printf("d plus done.\n");
switch (m) {
case 1: {
if (d > 31) d = 1, m += 1;
if (m == 13) m = 1, y += 1;
}; break;
case 2: {
if((y % 4 == 0 && y % 100 != 0) || y % 400 == 0) {
printf("你输入的是闰年\n");
if (d > 28) {
d = 1; m += 1;
};
if (m == 13) {
m = 1; y += 1;
};
} else {
printf("不是闰年\n");
if (d > 27) d = 1; m += 1;
if (m == 13) m = 1; y += 1;
};
}; break;
default: {
printf("default, 30days per month.\n");
if (d > 30) d = 1, m += 1;
if (m == 13) m = 1, y += 1;
};
case 12: {
if (d > 31) d = 1, m += 1;
if (m == 13) m = 1, y += 1;
}; break;
case 10: {
if (d > 31) d = 1, m += 1;
if (m == 13) m = 1, y += 1;
}; break;
case 8: {
if (d > 31) d = 1, m += 1;
if (m == 13) m = 1, y += 1;
}; break;
case 7: {
if (d > 31) d = 1, m += 1;
if (m == 13) m = 1, y += 1;
}; break;
case 5: {
if (d > 31) d = 1, m += 1;
if (m == 13) m = 1, y += 1;
}; break;
case 3: {
if (d > 31) d = 1, m += 1;
if (m == 13) m = 1, y += 1;
}; break;
};
printf("%d年%d月%d日\n\n", y, m, d);
};
printf("final:%d年%d月%d日\n", y, m, d);
return 0;
}
// 遇到的坑:if语句,接多个句子,不用大括号,句子之间错误的使用了;而没有使用,
// 这导致,第一个;之后的语句 不再属于if结构体。
// 基本功很重要,如果不是亲自写代码,不会发现自己有这个问题
为什么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]区间的随机数作业 用随机数计算π
```cinclude
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;
}
<a name="cXdgg"></a>
#### 搞清楚对象文件的作用:
c语言中,程序从源代码到可执行文件,需要4个步骤:
- 第一步,预处理源代码,生成“待编译源码”
- 源代码就是我们自己写的代码;
- 其中包含了一些“需要预先处理的命令”,比如`#include<stdio.h>`;
- 预处理的作用,就是执行预处理命令;
- 具体操作就是将需要的内容复制到源代码中,供下一个阶段使用;
- 第二步,编译“待编译源码”,生成“对象文件”
- 待编译源码是完全体的代码,但仍旧是一种“人类创造出来的语言”(c语言)
- 但计算机看不懂,计算机只能看懂0和1
- 所以需要讲人类的语言“翻译成”计算机能看懂的语言
- 这个步骤就是编译
- 但是编译后,还有些对象需要链接?未解决
- 即对象文件是“已编译但未链接“
- 第三步,链接对象文件,生成可执行程序
- 比如需要调用`printf`函数,`printf`函数存在于预先编译好的printf.o文件中,链接器负责将printf和调用printf的代码合并,得到可执行程序
<a name="SK5yi"></a>
#### 如何在macOS终端运行c程序?
- 需要安装xcode
- 创建hello.c文件
- 通过终端编译文件`gcc hello.c`
- 编译通过,会在当前目录生成`a.out`文件
- 通过终端执行`a.out`文件`./a.out`
- 如果是多个c语言源文件,则需要通过`gcc file1.c file2.c -o main.out`生成名为main.out的文件,再通过`./main.out`执行
<a name="Sem90"></a>
#### 编译+执行太麻烦了 如何用vim直接执行?
好像不可以... 用IDE比较方便
<a name="iJuAZ"></a>
#### c语言中的&和*分别是什么意思?未解决
`*`代表指针、或取值(访问指针)<br />`&`代表取地址?<br />`int *p`<br />`printf("%d", p)`——用`%d`来输出地址
<a name="FQwg7"></a>
#### 关于指针:
- 定义一个指针变量`int *p`
- 把变量地址赋值给指针
```c
int a = 100;
int *p;
p = &a; //把变量的地址赋值给指针
访问指针变量中可用地址的值
int *p;
printf("%d", *p); /访问指针变量中可用地址的值
指针的类型可以进行运算
++ -- + -
const int MAX = 3;
int main() { int var[] = {1, 2, 3}; int i, *ptr;
ptr = var;
for (i = 0; i < MAX; i++) {
printf("var[%d]的地址是:%p\n", i, ptr);
printf("var[%d]的值是:%d\n", i, *ptr );
ptr++;
}
return 0;
}
- 指针也可以用来比较`== < >`
- 当两个指针`p1 p2`指向了两个相关的变量,比如同一个数组中的两个不同元素,则可以对两个指针进行大小的比较
```c
#include <stdio.h>
const int MAX = 3;
int main() {
int var[] = {1, 2, 3};
int i, *ptr;
ptr = var;
i = 0;
while(ptr <= &var[MAX - 1]) {
printf("var[%d]的地址是:%p\n", i, ptr);
printf("var[%d]的值是:%d\n", i, *ptr );
ptr++;
i++;
}
return 0;
}
void getSeconds(unsigned long *par);
int main() { unsigned long sec;
getSeconds( &sec );
printf("number of seconds:%ld\n", sec);
return 0;
}
void getSeconds(unsigned long par) { par = time(NULL); return; }
<a name="Vv3YK"></a>
#### c中的“宏”是什么?未解决
查找替换<br />`va_start(va_list argptr, lastparam);`<br />`va_start`是一个宏
<a name="Pe0EP"></a>
#### c如何注释?
- 行内注释`//`
- 块注释`/* ... */`
<a name="Fvd1L"></a>
#### 如何一次性声明多个头文件?
<a name="LhC9M"></a>
#### 为什么c程序里一定要写main函数
<a name="QyxT0"></a>
#### 学习可选参数的实现:
参考链接:[http://c.biancheng.net/view/344.html](http://c.biancheng.net/view/344.html)
```c
// 函数add() 计算可选参数之和
// 参数:第一个强制参数指定了可选参数的数量,可选参数为double类型
// 返回值:和值,double类型
#include <stdarg.h>
#include <stdio.h>
double add(int n, ... )
{
int i = 0;
double sum = 0.0;
va_list argptr;
va_start( argptr, n ); // 初始化argptr
for ( i = 0; i < n; ++i ) // 对每个可选参数,读取类型为double的参数,
sum += va_arg( argptr, double ); // 然后累加到sum中
va_end( argptr );
return sum;
}
int main(){
printf("%lf", add(4,1.1,2.2,2.3));
}