c语言
1 |
|
1 |
|
“the area of the Pentagon is -nan (ind)” 这句话表示 “五边形的面积为 -nan (ind)”,这是一个异常的计算结果,并非合理的面积数值。
其中,“nan” 是 “Not a Number” 的缩写,在编程、数学计算场景中常用来表示 “非数字”,特指因数据错误、公式误用等导致无法定义或无法表示的数值;“ind” 通常是 “indeterminate” 的缩写,意为 “不确定的”,整体 “-nan (ind)” 即 “不确定的非数字”,说明面积计算过程出现了问题,而非五边形存在一个 “-nan (ind)” 这样的面积。
为什么会出现这种结果?
可能导致该异常的常见原因包括:
输入数据错误:比如计算时输入的五边形参数(如边长、apothem(边心距)、外接圆半径等)为负数、空值,或与公式要求的参数类型不匹配(例如用文本代替数字)。
公式使用不当:例如计算正五边形面积时,误用了仅适用于不规则五边形的拆分公式,或公式中出现分母为 0、根号下为负数等数学上无意义的运算。
1 |
|
- 解三角形面积函数模板
1 | double area(double x,double y,double z){ |
判断完全平方数:从1开始的连续奇数的和,是一个完全平方数
三元运算符的语法
condition ? expression1 : expression2
条件 ? 表达式1:表达式2
如果条件为真,则执行表达式1,并返回表达式1的结果,如果为假就执行并返回表达式2的结果
configure:配置
1 |
|
1 |
|
辗转相除法求最大公约数,数学定理gcd(m,n)=gcd(n,r)
模板:
1 | r=m%n; |
r是remainder(余数)的缩写,m是大数,n是小数,后续充当被除数和除数的角色
while既充当判断又充当循环,当r=0的时候n就是m和n的最大公约数,当余数不为零的时候,n变成m,r变成n(倒着赋值),再次取模进行循环,最终返回n
判断素数
1 |
|
判断素数首先分类讨论1(不是素数),2(是素数)的特殊情况,然后找一个循环遍历的限制limit是m的开方再加1,用i到limit的数字开始遍历取模m,每取模一次,进行依次if判断,如果正好被整除,说明不是素数,如果不被整除,说明是素数
模板
1 | int m,i,limit; |
数字金字塔
1 |
|
数字金字塔先从每一行循环开始,一行循环中又分为两个循环递归,一个是空格循环,另一个是数字循环,空格循环是逐级递减的,所以要从n-i开始,其他条件不变(这里理解不了的话直接记模板:for(j=1;j<=n-i;j++)),数字循环是递增,注意递增也分为逐行递增和奇数个数式递增
不用记模板,我现在又理解了,先执行完空格循环,再开始执行数字循环,也就是说,j的开始是一个空格,打出一个空格后进行下一个循环,此时j递增为2,再打出一个空格,也就是说,一连串打出了n-i个空格,当n=5,i=1时,j=4,所以打出了4个空格
所以说并列的循环是先执行完整第一个循环,第一个循环结束后再执行第二个循环,而嵌套的循环是交叉的,假设嵌套两个循环,第一个循环执行一次,开始执行第二个循环,第二个循环完整执行结束后再开始执行第一个循环第二次
逐行递增
首先要注意逐行递增是随行数递增,所以是从i开始,边界值不要带成n,这里的边界值也是递增的,当i=1的时候,循环只进行一次就结束了,然后一个大循环完整执行一次,进入下一个大循环也就是i=2,在轮到数字循环时循环了两次,所以打了两个数字,以此类推


奇数式循环
也就是把空格替换成数字,就用奇数递推表达式i*2-1,这样就可以居中对齐了

计算复数
1 |
|
记账
安装MYSY2
mysy2是
我的mysy2安装路径E:\msys64
C语言函数只能返回一个值。你需要通过指针参数来“返回”多个值,或者使用结构体。
&表示接收变量
1 |
|
统计字符
1 |
|
获取字符串用getchar(),用for循环读取的时候用char c容器来承载
阶乘计算e
我写的多函数调用
1 |
|
简化模板
1 |
|
factorial:阶乘
累成或累加时记得初始化一个值,这个值对应i=0时的计算(我习惯以i=1作为循环的开始)注意在sum()中调用fact()函数时输入的值是i不是n,这样fact才能随着sum里循环的进行而同步进行,相当于复合函数
字母金字塔
1 |
|
哥德巴赫猜想
1 |
|
拉丁循环
1 |
|
回文序列
1 |
|
char s[10001]; // 因为n最大为10^4,所以数组大小设为10001(包含结束符’\0’)
1 |
|
输入一个小写字母:scanf(" %c",&d);
数据类型选择:因为输入的c和d范围到10^9,所以用long long类型存储,避免整数溢出。计算死亡率时,将其中一个数转换为double类型(如(double)d),保证浮点运算的精度。
输入处理:使用scanf(“%lld %lld”, &c, &d)读取一行中的两个整数。
输出格式:死亡率需要保留三位小数并以百分数形式输出,所以用%.3f%%的格式,其中%.3f控制小数位数,%%用于输出百分号本身
在 C 语言中不建议使用 char d[1000000001](即 10 亿个字符的数组),核心原因是内存超限,具体分析如下:
- 内存占用过大,超出实际可用范围
每个 char 类型在 C 语言中占 1 字节,char d[1000000001] 需占用约 1GB 内存(10 亿字节 ≈ 953.7MB,接近 1GB)。
而 C 语言中局部变量(如在 main 函数内定义的数组)默认存储在 栈内存 中,栈的大小通常非常有限(Windows 系统默认栈大小约 1MB~8MB,Linux 约 8MB),1GB 远超栈的承载能力,会直接导致 栈溢出(stack overflow),程序崩溃。 - 即使改为全局变量 / 静态变量,仍不实用
若将数组定义为全局变量(如 char d[1000000001]; 放在 main 函数外)或静态变量(static char d[1000000001];),其存储位置会变为 全局 / 静态存储区(该区域空间较大,通常能容纳 GB 级数据)。
但这种做法仍不推荐:
浪费内存:大多数场景下,不需要 10 亿个字符的存储空间(比如存储普通输入的字符串,几百、几千字节已足够),过大的数组会长期占用大量内存,降低程序运行效率。
兼容性问题:部分嵌入式系统、小型设备的内存总量可能不足 1GB,此时程序会因内存不足无法运行。 - 实际场景的替代方案
若需存储超长字符串(如大文件内容),更合理的做法是:
先预估实际需要的最大长度,定义合适大小的数组(如 char d[10001] 对应 1 万个字符,足够应对绝大多数输入场景);
若长度不确定,使用 动态内存分配(malloc/realloc),根据实际需求申请内存,用完后释放(避免内存浪费);
对于超大文件(GB 级),采用 “分块读取” 的方式,无需一次性存储全部内容。
double rate = (double)d / c * 100;
double rate:定义一个 double 类型(双精度浮点数)变量 rate,用于存储最终的百分数结果(支持小数,比如 3.14%)。
(double)d:将变量 d 强制转换为 double 类型(浮点数)。
关键原因:C 语言中,若两个整数(如 int/long long 类型的 d 和 c)直接相除,结果会自动 “取整”(丢弃小数部分,比如 3/2=1 而非 1.5)。
强制转换 d 为 double 后,后续运算会自动按 “浮点数规则” 计算,保留小数精度(比如 3.0/2=1.5)。
/ c:用转换后的浮点数 d 除以变量 c,得到 “占比小数”(比如 d=3、c=10 时,3.0/10=0.3,即 30% 的小数形式)。
- 100:将 “占比小数” 乘以 100,转换为百分数形式(比如 0.3*100=30.0,即 30.0%)
#include <stdio.h>
int main() {
long long c,d;
scanf(“%lld %lld”,&c,&d);
double rate = (double)d/c *100;
printf(“%.3%%\n”,rate);
return 0;
}
#include <stdio.h>
int main() {
long long c, d;
scanf(“%lld %lld”, &c, &d);
double rate = (double)d / c * 100;
printf(“%.3f%%\n”, rate);
return 0;
}
一个整数对 10 取余,结果就是它的个位数字。例如,114 % 10 = 4
取绝对值:通过abs函数获取a的绝对值abs_a,确保后续计算不受符号影响。注意前面要声明#include<stdolib.h>
int 表示有符号整数,既可以存储正整数,也可以存储负整数和零
fabs 函数:取差值的绝对值
double a = 80.0, b = 90.0, c = 85.0; // 假设输入的成绩
int s = a * 0.2 + b * 0.3 + c * 0.5;
int s不会把0.2那些强制转化为0吗
1 | double a = 80.0, b = 90.0, c = 85.0; // 假设输入的成绩 |
第一步:计算各部分乘积
a * 0.2:a 是 double,0.2 是 double(C 语言中直接写的小数默认是 double),结果还是 double(如 80.0 * 0.2 = 16.0);
b * 0.3:同理,90.0 * 0.3 = 27.0(double 类型);
c * 0.5:85.0 * 0.5 = 42.5(double 类型)。
第二步:求和
三个 double 类型的结果相加:16.0 + 27.0 + 42.5 = 85.5(依然是 double 类型)。
第三步:赋值给 int 变量
只有这一步才发生类型转换:将 double 类型的 85.5 赋值给 int 类型的 s,C 语言会直接截断小数部分,得到 85(不是四舍五入)。
关键澄清:“0.2 被转化为 0” 的情况只在一种场景下发生
如果你的代码写成这样(把 0.2 当成整数处理),才会出现 0.2 被转化为 0 的情况:
1 | int rate = 0.2; // 错误写法:直接把小数赋值给 int |
这里的问题是:0.2 直接赋值给 int 变量 rate,会被截断为 0,后续计算自然出错。
但原代码中 0.2 是作为浮点数参与计算的,没有提前赋值给 int,所以不会有这个问题。

代码核心错误是 sum 未初始化,且循环内重复定义变量,导致结果异常(通常是随机垃圾值)。以下是详细分析和修正方案:
一、错误原因拆解
变量未初始化:long long sum 只在循环内定义,且定义时未赋初始值(C 语言中局部变量默认是随机值,不是 0)。更严重的是:循环内每次迭代都会重新创建 sum(之前的累加结果被销毁),循环结束后,printf 访问的是循环外未定义的 sum(编译可能警告,运行结果随机)。
循环内重复定义变量:把 long long sum 写在 for 循环内部,导致每次循环都生成一个新的 sum 变量,无法累积求和(相当于每次只加了当前 i,但下一次循环又重新开始)。
能被 400 整除的年份(如 2000 年、2400 年),一定是闰年;
能被 4 整除但不能被 100 整除的年份(如 2004 年、2020 年),是闰年;
其他情况(如 1900 年、2021 年),不是闰年
语法拆解
先明确核心语法:条件 ? 成立时的值 : 不成立时的值整行代码可拆分为 3 个关键部分:
1 | printf("%c\n", // 1. 输出部分:%c 表示输出单个字符,\n 换行 |
二、执行流程(从左到右,先判断再取值)
三元运算符是 “短路求值”:先判断条件,满足则直接取 “成立值”,不满足才继续判断后续逻辑。具体步骤:
先判断第一层条件 a < b:
若 a < b 为 真(比如 a=1, b=2):直接取 ‘<’ 作为整个表达式的结果,后续嵌套的三元运算符不再执行;
若 a < b 为 假(比如 a=3, b=2 或 a=2, b=2):进入第二层嵌套判断 a == b。
再判断第二层条件 a == b:
若 a == b 为 真(比如 a=2, b=2):取 ‘=’ 作为表达式结果;
若 a == b 为 假(比如 a=3, b=2):取 ‘>’ 作为表达式结果。
最后 printf 输出第二步得到的字符(<、= 或 >),并换行。
最大最小值
- if后面不用{}吗
在 C 语言中,if 后面的 {} 不是必须的,但用法有严格规则,核心取决于 if 要控制的代码行数 —— 这也是新手容易出错的点,结合你的代码和常见场景详细说明:
一、核心规则:{} 的作用是 “代码块”
{} 用于将多条语句包裹成一个 “代码块”,让 if(或 else、循环等)能控制整个代码块。如果 if 后面只有一条语句,{} 可以省略;如果有多条语句,{} 必须加,否则 if 只能控制第一条语句,后续语句会不受条件影响直接执行。
1 |
|
提取数字后两位用n%100
用循环之前一定要初始化变量为一个定值
1 | for(a=2;a<=limit;a++){ |
关键在于 break 的位置
第一个 break:if 语句块内部的 break;。这个 break 只有在 if 条件为真时才会执行。如果 n%i==0,则执行 isprime=0; 和这个 break;,循环结束。
第二个 break:if 语句块外部的 break;。这个 break 无论 if 条件是真是假,都会在 if 语句块执行完后立即执行。
最大的差
第一行输入一个整数 nn(2≦n≦1052≦n≦105)。
第二行输入 nn 个整数 a1,a2,…,ana1,a2,…,an,每个整数满足 −105≦ai≦105−105≦ai≦105,用空格隔开。
输出描述:
输出一个整数 SS,表示所能获得的最大差值。
1 |
|
这样声明和初始化变量的核心目的是确保第一次读取的整数能正确更新 max 和 min,同时让代码逻辑严谨、无遗漏,具体原因如下:
- 为什么 max 要初始化为 INT_MIN(整型最小值)?
max 的作用是记录所有输入整数中的 “最大值”,需要通过逐一比较来更新。
若初始化为 0 或其他固定数值:
假设输入的所有整数都小于这个初始值(比如输入 -5, -3, -1),那么 max 会一直保持初始值(比如 0),而不会更新为真正的最大值(-1),导致结果错误。
初始化为 INT_MIN(比如 32 位系统中是 -2147483648):
这是 C 语言中整型能表示的最小可能值,任何输入的整数(只要是合法的 int 类型)都会大于或等于它。
因此,第一次读取的整数必然会满足 num > max,从而将 max 正确更新为第一个数;后续读取的数再与当前 max 比较,逐步找到真正的最大值。 - 为什么 min 要初始化为 INT_MAX(整型最大值)?
min 的作用是记录所有输入整数中的 “最小值”,逻辑与 max 相反,同样需要通过逐一比较更新。
若初始化为 0 或其他固定数值:
假设输入的所有整数都大于这个初始值(比如输入 2, 4, 6),那么 min 会一直保持初始值(比如 0),而不会更新为真正的最小值(2),导致结果错误。
初始化为 INT_MAX(比如 32 位系统中是 2147483647):
这是 C 语言中整型能表示的最大可能值,任何输入的整数(只要是合法的 int 类型)都会小于或等于它。
因此,第一次读取的整数必然会满足 num < min,从而将 min 正确更新为第一个数;后续读取的数再与当前 min 比较,逐步找到真正的最小值。 - 为什么要声明 num 作为临时变量?
每次循环需要读取一个整数,不能直接将读取的数与 max/min 比较(scanf 需要一个变量的地址来存储输入值)。
num 作为 “中转站”,先存储当前读取的整数,再用它来更新 max 和 min,避免重复读取输入(重复读取会导致跳过部分输入值)。
示例逻辑:
读取第一个数 → 存入 num → 用 num 更新 max 和 min → 读取第二个数 → 覆盖 num → 再用新 num 比较更新 max/min → 循环直至读完所有数。
% 运算符只能用于整数类型。
题目要求处理的整数范围是 [-10^9, 10^9]。虽然 double 可以存储这个范围内的数值,但它是一种浮点数,在进行数学运算(如取模 % 和幂运算 pow)时,会因为精度丢失而产生错误。
使用 long long 确保能容纳 10^9
pow 函数的返回值是 double:
pow(10, i) 是 math.h 中的函数,它接受两个 double 类型的参数,并返回一个 double 类型的结果。
即使你传入的是整数 10 和 i,它们也会被隐式转换为 double,函数返回值也是 double。
/ 和 % 运算符不能用于 double 和 int 的混合运算:
在 C 语言中,% (取模) 运算符只能用于整数类型 (int, long, long long 等)。
/ (除法) 运算符可以用于 double,但当你将 double 类型的 pow 结果与 long long 类型的 n 进行除法时,结果会变成 double。
最后,你试图对这个 double 类型的结果进行 % 10 操作,这在 C 语言中是非法的,因为 % 不支持浮点数。
强制类型转换无法解决根本问题:
即使你在 n / pow(10, i) 前面加上 (long long) 强制转换,比如 (long long)(n / pow(10, i)),这在数学上可能成立,但在编程实践中非常危险。
因为 pow(10, i) 返回的是 double,对于大数 i(例如 i=9),pow(10, 9) 可能无法精确表示为 double,会产生微小的误差。当这个有误差的 double 被强制转换为 long long 时,可能会向下取整,导致结果错误。
例如,pow(10, 9) 理论上应该是 1000000000.0,但由于浮点数精度限制,它可能是 999999999.999999...,强制转换后就变成了 999999999,从而导致计算错误。
数位之和
1 |
|
gitoskip
1 |
|
二维斐波那契数列
1 |
|
循环存值
1 |
|
左侧严格小于计数(一个数的左侧有几个数小于它)
1 |
|
上端代码有个错误
为什么错了?
在 C 语言中,数组的索引是从 0 开始的。
你声明了 int a[100];,这意味着你可以安全访问的索引是 a[0], a[1], ..., a[99]。
在 for 循环中,你从 i = 1 开始,一直循环到 i = n。
当 n = 5 时,你会访问 a[1], a[2], a[3], a[4], a[5]。
问题来了:a[5] 是一个非法的索引! 因为你的数组只有 100 个元素,最大合法索引是 99。访问 a[5] 会写入到不属于 a 数组的内存区域,这会导致:
未定义行为 (Undefined Behavior):程序可能崩溃、输出错误结果,或者看起来正常运行但实际上已经破坏了内存。
在示例2中导致错误输出:输入 3 和 1 6 6,你的程序实际读取的是 a[1]=1, a[2]=6, a[3]=6。但在计算 b[3](即 a[3] 左侧小于它的数)时,它会去检查 a[0], a[1], a[2]。然而,由于你没有初始化 a[0],它的值是随机的(垃圾值)。如果这个垃圾值恰好小于 6,就会被错误地计入 count,导致最终输出错误。
1 |
|
一处错误 (ave = sum / n;):
sum 是 long long,n 是 int。
sum / n 是一个整数除法!即使你把它赋值给 double ave,这个除法操作本身也是先进行整数运算,再将结果转换为 double。
例如,sum=5, n=2,sum/n 的结果是 2(整数除法),然后 ave 会变成 2.0,而不是正确的 2.5。
修正:必须强制转换其中一个操作数为 double,例如 double ave = (double)sum / n;。
long long max = INT_MIN, min = INT_MAX;
问题分析:
你声明了 max 和 min 为 long long 类型,这是正确的。
但你用 INT_MIN 和 INT_MAX 来初始化它们。INT_MIN 和 INT_MAX 是 int 类型的宏常量。
虽然 C 语言会将 int 自动转换为 long long,但这在逻辑上是不严谨的。更重要的是,INT_MIN 的值是 -2147483648,而 INT_MAX 的值是 2147483647。
如果数组中的所有元素都大于 INT_MAX(例如,全是 3000000000),那么 max 的初始值 INT_MIN 会小于所有元素,所以 max 最终会被正确更新为 3000000000。
但是,如果数组中的所有元素都小于 INT_MIN 呢? 比如全是 -3000000000。由于 INT_MIN 是 -2147483648,它比 -3000000000 大,所以 if (max <= a[i1]) 这个条件永远不会成立,导致 max 一直保持为 INT_MIN,而不是真正的最大值 -3000000000。这会导致极差计算完全错误。
同理,对于 min,如果所有元素都大于 INT_MAX,也会出错。
printf(“%d”, count); // ❌ 这里使用了 count,但之前没有声明它
但在 for 循环内部,你虽然写了 int c = i % 10, count = 0;,但这行代码是在 while 循环内部的。
这意味着 count 变量的作用域(Scope)仅限于 while 循环内部。当 while 循环结束时,count 变量就“消失”了,不能再在循环外部访问。
✅ 解决方法
你需要将 count 变量的声明移到 for 循环内部、while 循环外部,这样它才能在 printf 中被访问。
计数问题
1 |
|
约瑟夫环报数出队问题
1 |
函数返回值的类型是由函数定义时指定的类型决定的。例如,若函数定义为int func(),则无论return语句中表达式的类型如何,返回值最终都会被转换为int类型。
选项 B 错误,return语句中的表达式类型会被隐式转换为函数定义时指定的返回类型。
在 C 语言中,static 是一个存储类修饰符,用于改变变量的存储位置和生命周期,核心特点是「一次初始化、全程持久化」,且访问范围受定义位置限制。
一、static 变量的核心特性
1. 存储位置与生命周期
- 普通局部变量(无
static):存储在「栈区」,生命周期仅限于函数调用期间——函数进入时创建,函数返回时销毁,每次调用都会重新初始化。 - static 变量(局部/全局):存储在「静态数据区」,生命周期与程序一致——程序启动时分配内存,程序结束时才释放,仅在第一次初始化时赋值一次,后续调用/使用会保留上次的值。
2. 初始化规则
- 若未显式赋值,会被编译器自动初始化为 0(普通局部变量未初始化时是随机垃圾值)。
- 初始化表达式必须是「编译期常量」(如
static int a = 10;合法,static int a = rand();非法,因为rand()是运行时结果)。
3. 访问范围(作用域)
- static 局部变量:作用域仅限于定义它的函数/代码块内(和普通局部变量一样),但生命周期是全局的(保留值)。
- static 全局变量:作用域仅限于当前源文件(.c 文件),其他源文件无法通过
extern访问(普通全局变量默认是跨文件可见的)。
二、static 局部变量(最常用场景)
结合你之前代码中可能遇到的函数调用场景,用例子理解:
1 |
|
- 输出结果:
1 2 3 - 关键:
count是static局部变量,第一次调用时初始化count=0,之后每次调用都保留上次自增后的值,不会重新初始化。
如果去掉 static(普通局部变量):
1 | int test() { |
- 输出结果:
1 1 1(每次调用count都是新的 0,自增后为 1)。
三、static 全局变量(跨文件场景)
假设有两个源文件:a.c 和 b.c:
1. a.c(定义 static 全局变量)
1 | static int num = 100; // static 全局变量:仅 a.c 可见 |
2. b.c(尝试访问 num)
1 | extern int num; // 试图声明并访问 a.c 的 num |
- 作用:
static修饰全局变量时,用于「隐藏变量」,避免跨文件命名冲突(普通全局变量若多个文件同名,会导致链接错误)。
四、和你之前代码的关联
你之前的代码中没有使用 static 变量,但如果需要优化(比如减少重复计算),可以用 static 局部变量。例如,优化 IsSquare 函数(缓存常用平方数判断结果):
1 | int IsSquare(int n) { |
这里 static 变量 max_sqrt 和 max_square 会保留上次计算的结果,避免重复计算相同的平方数判断。
总结 static 变量的核心记忆点
| 特性 | static 局部变量 | static 全局变量 | 普通局部变量 | 普通全局变量 |
|---|---|---|---|---|
| 存储位置 | 静态数据区 | 静态数据区 | 栈区 | 静态数据区 |
| 生命周期 | 程序全程 | 程序全程 | 函数调用期间 | 程序全程 |
| 初始化 | 默认 0,仅初始化一次 | 默认 0,仅初始化一次 | 随机值,每次调用初始化 | 默认 0,仅初始化一次 |
| 作用域 | 所在函数/代码块 | 所在源文件 | 所在函数/代码块 | 所有源文件(extern可见) |
简单说:static 局部变量是「函数内的全局值」(保留状态),static 全局变量是「文件内的私有值」(隐藏跨文件访问)。
注意for,if,while有无花括号,没有就循环或判断一条代码
static就是可以保存函数里面的值,在再次调用时使用上一次调用保存下来的值
static int i = 0;:只在第一次调用函数时初始化为 0,之后的调用会保留上一次的值。
int s = 0;:每次调用函数时都会重置为 0。
循环条件是 i <= m,循环体是 s += i;,然后 i++。
若函数类型未显式说明,其隐含类型为int。例如,函数fun() { return 10; }会被默认视为返回int类型的函数。
函数调用中,实参的数量由逗号分隔的参数个数决定,与参数是否为表达式无关。在Func(exp1, exp2+exp3, exp4exp5)中,三个参数分别是exp1、exp2+exp3、exp4exp5,因此实参数量为 3
// 全局变量(整个程序可见)
int x = 5;
int y = 6;
// incxy() 函数:无参数,直接操作全局变量
void incxy() {
x++; // 直接访问全局变量x,自增
y++; // 直接访问全局变量y,自增
}
int main() {
int x = 3; // 局部变量x(仅main函数内可见)
incxy(); // 调用函数时无需传参,函数内部操作的是全局x、y
printf(“%d,%d”, x, y); // 输出局部x=3,全局y=7
return 0;
}
局变量x未显式初始化,默认值为0
#include <stdio.h>
int x; // 全局变量,初始值为 0(未显式初始化)
int f(); // 函数声明
int main(void)
{
int a = 1; // 局部变量 a,初始值为 1
x = a; // 全局变量 x 被赋值为 1
a = f(); // 调用函数 f(),将返回值赋给局部变量 a
{ // 新的作用域块
int b = 2;
b = a + b; // b = a + 2
x = x + b; // 全局变量 x = x + b
}
printf("%d %d", a, x); // 输出 a 和 x
return 0;
}
int f()
{
int x = 4; // 局部变量 x(与全局 x 不同)
return x; // 返回 4
}
if (m % i == 0) return 0;
return 1;
若能则返回 0(不是质数),否则返回 1(是质数)。
int fun (int m)
{
int cur_digit, old_digit = 10;
if (m < 0){
m = -m;
}
do{
cur_digit = m % 10;
if( cur_digit >= old_digit){
return 0;
}
old_digit = cur_digit
;
m/=10
;
}while (m != 0);
return 1;
}
初始化值不是0或一的函数
res[idx++] = i; // 28. 把当前整数i存入结果数组,并更新索引
res[idx] = i:将 i 存入数组 res 的第 idx 个位置(比如 idx=0 时,存入 res[0])。
idx++:存入后,idx 加 1(准备存储下一个符合条件的整数,比如下次存入 res[1])。
整数的持续性
从任一给定的正整数 n 出发,将其每一位数字相乘,记得到的乘积为 n1。以此类推,令 ni+1 为 ni 的各位数字的乘积,直到最后得到一个个位数 nm,则 m 就称为 n 的持续性。例如 679 的持续性就是 5,因为我们从 679 开始,得到 6×7×9=378,随后得到 3×7×8=168、1×6×8=48、4×8=32,最后得到 3×2=6,一共用了 5 步。
本题就请你编写程序,找出任一给定区间内持续性最长的整数。
1 |
|
校门外的树
1 |
|
1 |
|
二维数组
1 |
|
判断上三角矩阵
1 |
|
矩阵转置
1 |
|
杨辉三角
1 |
|
扫雷
1 |
|
替换工具
1 |
|
strcmp 函数: 这是 C 语言中用于比较两个字符串是否相等的函数。如果两个字符串相等,它返回 0。
比如数组 digit 存了 3 个元素 [‘3’,‘2’,‘1’],它们的索引分别是 0(存 ‘3’)、1(存 ‘2’)、2(存 ‘1’)。也就是说:数组的 “最后一个有效元素的索引” = 有效元素个数 - 1。
添加逗号
1 |
|
查找
1 |
|
scanf(“%s”, s) 是「读整串不含空白的字符」,getchar() 是「读单个字符(包括空白字符)」
凯撒密码
1 |
|
阶乘计算cos
1 |
|
理解函数接口
1 |
|
1 |
|
数组名tab是地址常量,不能直接赋值
二维数组的下标从 0 开始,所以数组中[2][3]其实是第三行第字符串"China"包含 5 个字符(‘C’、‘h’、‘i’、‘n’、‘a’),加上自动添加的'\0',共 6 个字符;
不要算上双引号
1 |
|
1 | 5 3 |
简化的插入排序
1 |
|
1 |
|
发现没排序,改进版
1 |
|
寻找鞍点
1 |
|
螺旋方阵
1 |
|
1 |
|
为什么这里不用 getchar?
getchar() 的核心作用是「逐个读取单个字符」(包括空格、回车、制表符等空白字符),而我们的场景是「读取完整的选项字符串(无空格)」,用 scanf("%s") 更适配,具体原因分 3 点:
1. 需求匹配:scanf("%s") 天生适配 “无空格字符串” 读取
题目中每个选项是单个连续字符串(比如 "apple" "banana"),没有空格 / 回车分隔(输入时选项之间用空格 / 回车分隔):
scanf("%s")会自动跳过前置空白字符(空格、回车、制表符),读取到下一个空白字符为止,正好能 “精准读取单个选项字符串”;- 而
getchar()是逐字符读,如果用它读取字符串,需要手动循环读字符、手动判断终止(遇到空白字符停止)、手动加\0,代码会更繁琐。
比如用 getchar 实现读取一个选项的逻辑:
c
1 | // 用getchar读取单个选项(替代scanf("%s", s[i])) |
对比 scanf("%s", s[i]),代码简洁度差很多,完全没必要。
2. 避免 getchar 的 “残留字符坑”
如果混用 scanf(读数字 / 字符串)和 getchar,很容易遇到「缓冲区残留换行符」的问题:
比如先执行 scanf("%d", &T) 读取题目数量,输入 3 后按回车,缓冲区会残留 \n;如果紧接着用 getchar 读选项,第一个读到的会是 \n,而非选项字符,需要额外处理(比如手动清空缓冲区)。
而 scanf("%s", s[i]) 会自动跳过这些残留的空白字符,无需额外处理,更适合本题的输入流程。
3. 二维数组场景下,getchar 无优势
本题中二维数组的每个一维数组(s[0]/s[1]等)是独立的字符串存储区,scanf("%s") 能直接把字符串写入对应区域;而 getchar 只能逐字符写入,需要手动管理每个字符的下标,增加代码复杂度。
三、补充:scanf("%s") 的注意事项(为什么数组长度设为 601)
scanf("%s") 读取字符串时,会自动在末尾添加 \0(字符串结束符),因此:
- 如果题目要求选项字符串最长 600 个字符,数组长度需要设为
601(600 个有效字符 + 1 个\0),避免缓冲区溢出。
总结
| 问题 | 答案 |
|---|---|
| 二维数组为何不用 & | s[i] 是一维数组名,本身就是地址,等价于 &s[i][0],符合 scanf 要求 |
| 为什么不用 getchar | 1. scanf (“% s”) 适配 “无空格字符串”,代码更简洁;2. 自动跳过空白字符,避免缓冲区坑;3. getchar 需手动循环 / 加 \0,无优势 |
简单来说:本题的输入场景(读取无空格的独立字符串)是 scanf("%s") 的 “最优解场景”,而 getchar 更适合「逐字符处理」「读取含空格的输入」等场景,因此这里完全不需要用 getchar。
一维字符数组(对应题目中 s[i])
1 | // 定义一维数组:存储"apple"(末尾自动加\0,共6个字符) |
str是数组名,它等价于&str[0](str[0]是数组第一个元素'a',&str[0]是'a'的内存地址);
考试口诀应用答案生成器
1 |
|
int l, r;直接在循环里面声明不会导致被覆盖吗,不用在循环外面用这种形式int l[100],r[100],c1[100],c2[100];声明吗
每次循环迭代都会重新创建局部变量,用完即销毁,不存在 “覆盖导致逻辑错误” 的问题。
1. 变量的 “作用域” 和 “生命周期” 是关键
- 作用域:循环内声明的
l、r、c1、c2,作用域仅限于当前这一次循环迭代(花括号{}内部); - 生命周期:变量从本次迭代开始时创建,迭代结束时销毁(内存被释放)。
举个例子:
1 | for (int i = 0; i < 2; i++) { |
- 第 1 次循环:读取的
l、r是 “本轮专属” 的变量,处理完就销毁; - 第 2 次循环:会重新创建一组全新的
l、r,和上一轮的变量毫无关系,不存在 “覆盖上一轮值” 的问题(因为上一轮的变量已经没了); - 我们的需求是 “读取一次操作参数,立即处理,处理完不需要留存”,所以根本不需要把所有操作的
l、r都存起来。
2. 反过来说:循环内声明的优势
- 节省内存:每次只用 4 个字节(int l/r)+ 2 个字节(char c1/c2),用完就释放;如果用数组存 100 个操作的参数,哪怕只执行 2 次操作,也会占用 100 组参数的内存,完全浪费;
- 逻辑清晰:变量只在需要的时候创建,避免 “全局 / 外部变量被意外修改” 的风险;
- 代码简洁:不需要额外维护数组下标(比如
l[i]),直接用变量名即可,减少出错概率。
读取一次操作参数 → 立即执行替换 → 丢弃本次参数 → 处理下一次,全程不需要留存任何操作的参数,因此用数组存储完全是 “画蛇添足”。
三、对比两种写法的差异(更直观)
| 写法 | 适用场景 | 内存占用 | 逻辑复杂度 |
|---|---|---|---|
| 循环内声明 l、r、c1、c2 | 读取一次处理一次,无需留存参数 | 极小 | 低 |
| 数组 l [100]、r [100] 等 | 需要留存所有操作参数(验证 / 回显 | 较大 | 高,需要下标 |
要加下标的情况是
场景 1:先读取所有操作参数,验证合法性后,再统一执行替换
先把所有m次操作的参数全读进来存好 → 逐个检查参数是否合法 → 若全部合法,再执行所有替换;若有非法参数,直接报错,不执行任何替换。
举个具体例子
假设题目新增要求:
输入的操作参数必须满足 1≤l≤r≤n,否则判定为非法操作,直接输出 “error”,不修改原字符串。
这种情况下,必须先把所有操作参数存起来验证,而不能读一个执行一个 —— 比如:
- 输入:n=5,m=2,字符串
abcde - 操作 1:l=2, r=4, c1=b, c2=B(合法)
- 操作 2:l=6, r=8, c1=d, c2=D(非法,因为 n=5,l=6 超出范围)
如果不存参数,直接执行:
- 先执行操作 1,把
abcde改成aBcde; - 再读操作 2,发现非法,此时字符串已经被改乱,无法恢复。
如果用数组存参数,就能先验证再执行:
- 把操作 1、操作 2 的参数全存到数组里;
- 检查操作 2 的 l=6>5,判定非法,直接输出 “error”,原字符串
abcde完全没被修改。
场景 2:执行完所有替换后,回显所有操作的参数
为什么需要留存参数?
如果题目要求 “输出最终字符串的同时,列出所有执行过的操作”,比如:
输出格式:
最终字符串:gaaak
第 1 次操作:l=1, r=3, c1=g, c2=a
第 2 次操作:l=4, r=5, c1=k, c2=k
这种情况下,必须留存所有操作的参数—— 因为操作执行完后,原本读取的 l、r、c1、c2 已经被下一次循环的变量覆盖(如果是循环内声明的局部变量,甚至已经销毁),只有存在数组里,才能在最后遍历数组回显。
格式字符串中的空白字符作用
格式字符串里的空格、换行、制表符,会被scanf统一处理为:匹配输入流中任意数量的空白字符(0 个或多个)。
比如:
-
"%d %d %c %c"和"%d\n%d\t%c %c"效果完全一样; -
但如果格式字符串是
1
"%d%d%c%c"
(无空格),输入
1
2 5 a b
时:
- 第一个
%d读2,第二个%d跳过空格读5(因为%d自动跳空白); - 第一个
%c会读5后的空格(而非a),第二个%c读a,导致结果错误。
- 第一个
坑 1:%c读取到空白字符
比如输入:
1 | 2 5 |
执行scanf("%d %d %c %c", &l, &r, &c1, &c2);时:
%d读2,第二个%d跳过空格读5,然后格式字符串的空格匹配5后的换行符;- 但如果格式字符串是
"%d %d%c %c"(少一个空格),%c会读取5后的换行符,而非a。
解决办法:
-
方法 1:在
%c前加空格,强制跳过空白:"%d %d %c %c"(推荐); -
方法 2:手动跳过空白,比如用
1
getchar()
吃掉换行:
1
2
3scanf("%d %d", &l, &r);
getchar(); // 吃掉换行符
scanf("%c %c", &c1, &c2);
坑 2:输入字符时包含特殊符号
比如输入2 5 a-b,第二个%c会读取-而非b,因为%c只读取单个字符,遇到非空白就停止。
替换字符串
1 |
|
float 和double的区别
一、核心区别汇总表
| 特性 | float(单精度) | double(双精度) |
|---|---|---|
| 内存占用 | 4 字节(32 位) | 8 字节(64 位) |
| 有效数字位数 | 6~7 位十进制有效数字 | 15~17 位十进制有效数字 |
| 取值范围 | ±1.4×10⁻⁴⁵ ~ ±3.4×10³⁸ | ±4.9×10⁻³²⁴ ~ ±1.8×10³⁰⁸ |
| 存储格式(IEEE 754) | 符号位 (1) + 指数位 (8) + 尾数位 (23) | 符号位 (1) + 指数位 (11) + 尾数位 (52) |
| 运算速度 | 更快(硬件支持更基础) | 稍慢(精度高,计算量略大) |
| 默认浮点常量类型 | 不默认(如3.14是 double) |
默认(3.14f才是 float) |
| 适用场景 | 内存受限、精度要求低(如简单图形、传感器数据) | 精度要求高(如科学计算、金融、工程计算) |
二、关键特性通俗解释
1. 有效数字:最核心的差异(避坑重点)
“有效数字” 指能准确表示的数字位数,超出部分会被舍入(不是精确值):
-
1
float
:只能精确到 6~7 位十进制数。
例:
1
float f = 123456789.0f;
→ 实际存储的是近似值(因为 8 位有效数字超出了 float 的精度);
-
1
double
:能精确到 15~17 位十进制数。
例:
1
double d = 123456789012345.0;
→ 完全精确,无舍入误差。
2. 存储格式(IEEE 754 标准)
浮点型不是 “直接存储十进制数”,而是按 “符号 + 指数 + 尾数” 的科学计数法存储:
float:8 位指数位(能表示的指数范围更小)+ 23 位尾数位(精度低);double:11 位指数位(指数范围大)+ 52 位尾数位(精度高)。
简单理解:尾数位越多,精度越高;指数位越多,能表示的数的范围越大。
3. 浮点常量的默认类型
C 语言中,直接写的浮点常量(如3.14、10.0)默认是double类型;如果要表示float,必须加后缀f/F(如3.14f、10.0F)。
例:
1 | float f = 3.14; // 实际是double转float,有微小精度损失 |
4. 运算精度与速度
- 精度:
double>float(几乎所有场景下,double 的精度都能满足需求); - 速度:现代 CPU 对
double的运算优化已很好,两者速度差异极小(仅在嵌入式 / 极端性能场景下需考虑 float)。
三、实战选择建议
-
优先用
1
double
除非明确限制内存(如嵌入式系统中大量存储浮点数据),否则默认选
1
double
—— 精度更高,且几乎无性能代价,能避免绝大多数精度问题;
-
用
1
float
的场景:
- 内存紧张(如数组存 100 万个浮点数据,float 比 double 省一半内存);
- 硬件接口要求(如某些传感器输出 float 类型数据);
-
避坑点:
不要用浮点型存储 “需要精确值” 的场景(如金额),改用整数(如以分为单位存储金额),因为浮点型本身是近似存储(比如
1
0.1
无法用二进制浮点精确表示)。
const 是一个类型限定符(Type Qualifier),核心作用是限定变量 / 指针 / 函数参数等为 “只读”,即被const修饰的对象,其值 / 指向的内容不能被直接修改(编译阶段会检查,修改则报错)。
while(n--)的核心问题:“先判断后减 1,且乘法用了减 1 后的值”
T=6时,循环执行 6 次,对应 6 组输入。
n--是后置自减运算符,规则是:
- 先使用
n的 “原值” 判断循环条件(是否≠0); - 再将
n的值减 1; - 进入循环体后,使用的是减 1 后的 n 值。
对应阶乘计算,问题体现在两点:
1. 丢失了初始的n值(最大的乘数)
比如输入n=5,循环中第一次乘法用的是4,而非5—— 直接漏掉了阶乘中最关键的 “初始 n”,导致计算的是4×3×2×1×0(而非5×4×3×2×1)。
2. 多乘了一个0,直接让结果归 0
当n减到 1 时:
- 循环条件
n--判断为真(1≠0),n先减 1 变成 0; - 循环体执行
jie *= 0,结果直接变成 0,后续所有乘法都无法恢复。
取模const
1 | int factorialOfN(int n ) { |
C 语言不允许double与const int直接进行取模运算(取模%要求操作数为整数类型);
一开始password不是字符指针吗,为什么for循环的时候可以转化成数组
要理解 “字符指针password能像数组一样用password[i]访问”,核心是搞清楚C 语言中数组和指针的底层等价性—— 数组下标访问本质是 “指针 + 偏移” 的语法糖,两者在内存层面完全互通。下面分三步讲透:
一、先明确:char* password的本质
题目中传入的password虽然声明为 “字符指针”,但它指向的是一段连续的字符内存(字符串数组)的起始地址:
-
比如传入字符串
1
"abcd"
,内存布局是:
内存地址(示例) 0x100 0x101 0x102 0x103 0x104 存储内容 ‘a’ ‘b’ ‘c’ ‘d’ ‘\0’ -
password作为指针,存储的是起始地址0x100(即第一个字符 ‘a’ 的地址)。
二、password[i]的底层原理:下标访问 = 指针 + 偏移
C 语言规定:数组下标访问arr[i]等价于\*(arr + i)(指针解引用),不管arr是数组名还是字符指针。
拆解password[i]的计算过程:
password是指针(存起始地址,比如0x100);i是偏移量(单位:字节,字符占 1 字节);password + i= 起始地址 + i 个字节 → 指向第 i 个字符的地址;*(password + i)= 解引用该地址,拿到对应字符;password[i]是*(password + i)的 “语法糖”(简化写法)。
示例(password="abcd"):
password[0]=*(password + 0)= 取0x100地址的字符 → ‘a’;password[1]=*(password + 1)= 取0x101地址的字符 → ‘b’;password[3]=*(password + 3)= 取0x103地址的字符 → ‘d’。
password[i] - 'a'的计算过程(逐字符拆解)
假设password[i]是某个小写字母(比如 ‘a’、‘m’、‘z’),我们分情况计算:
情况 1:password[i] = 'a'(对应第一个小写字母)
1 | password[i] - 'a' = 'a' - 'a' = 97 - 97 = 0 |
→ 映射结果:0(对应第一个位置)
情况 2:password[i] = 'm'(中间字母)
1 | password[i] - 'a' = 'm' - 'a' = 109 - 97 = 12 |
→ 映射结果:12(a 是 0,b 是 1…m 是第 13 个字母,对应 12)
情况 3:password[i] = 'z'(最后一个小写字母)
1 | password[i] - 'a' = 'z' - 'a' = 122 - 97 = 25 |
→ 映射结果:25(对应最后一个位置)
通用规律
对任意小写字母c(a≤c≤z),c - 'a'的结果 = 该字母在 26 个小写字母中的 “序号 - 1”(a 是第 1 个→0,b 第 2 个→1…z 第 26 个→25),最终结果刚好落在0~25范围内。
a确实是最小的小写字母,但offset依然会变成负数,核心原因是:解密时要对a做 “向前移位” 操作(比如a向前移 1 位),此时0 - n就会得到负数。
1 | char* decodeWangzai(char* password, int n ) {//字符指针char*,c语言中字符串要么是数组要么是字符指针 |
字符串的替换,一次for循环干完所有
1 |
|
如何判断一个变量到底应该在循环内还是外声明
我认为判断那个变量是否是单次使用,或者循环的最终利用点不在变量本身,而是用变量作为一个跳板或者判断条件,这时候就可以在循环内
核心规则:如何判断变量该在循环内 / 外声明?
判断的核心是变量的 “作用域” 和 “生命周期” 需求,结合 4 个维度决策,下面用表格 + 示例清晰说明:
| 判断维度 | 声明在循环外 | 声明在循环内 |
|---|---|---|
| 1. 作用域需求 | 变量需要在循环外被使用(如后续计算、输出) | 变量仅在单次循环内使用,循环外无意义 |
| 2. 生命周期需求 | 需要保留值到下一次循环(如计数器、累加值) | 只需单次循环内有效,用完可销毁 |
| 3. 性能 / 开销 | 仅创建 / 销毁 1 次,开销小 | 每次循环创建 / 销毁,开销略大(编译器可优化) |
| 4. 代码可读性 | 变量复用,减少重复声明 | 变量作用域最小化,避免误修改 |
典型场景示例
场景 1:计数器(必须在循环外)
如代码中的 f/r/t,需要累计 10 次循环的结果,若放在循环内,每次循环都会重置为 0,无法统计,因此必须在循环外声明:
1 | // 错误:循环内声明计数器,每次循环f都会被初始化为0,最终f永远是0/1 |
场景 2:临时变量(建议在循环内)
如仅用于单次循环的临时计算变量,放在循环内可最小化作用域,避免后续代码误修改:
1 | // 示例:计算10个数的平方和 |
场景 3:复用变量(可循环外)
如代码中的 x/y,仅用于存储单次输入,无累计需求,循环内 / 外均可,但循环外更精简(少写一次声明)。
判断是否是对应的函数
就判断调用函数后返回的值是否是自己根据函数原理手算出来的值就行了
1 |
|
判断反素数
1 |
|
提取各位数字时,如果循环结束后要用原值做判断,或者循环,则必须在提取时加temp保护变量
如果以及知道边界并且边界较小,那么可以不用while循环和数组,直接用for循环和a1,a2,a3.
易错:数组是从0开始为索引的,套公式的时候需要下标减一
j–的时候小于变大于
数组作用域问题:n和k数组定义在第一个for循环内部,第二个for循环无法访问(超出作用域);
选择pow,还是循环
pow()的本质:返回浮点数,而非精准整数
pow(10, k[i]) 是 C 语言<math.h>库的函数,它的设计目标是计算任意实数的幂(比如pow(2.5, 3.2)),因此返回值是double类型(浮点数),而非整数。
浮点数的存储规则是 “近似表示”(比如无法精准存储0.1),这会导致两个致命问题:
问题 1:精度丢失,计算结果 “差一点”
比如你要算pow(10, 9)(10 的 9 次方 = 1000000000),但由于浮点数的精度限制:
- 实际返回值可能是
999999999.999999999(而非精准的 1000000000.0); - 当你把这个浮点数转成整数时,会直接截断小数部分,变成
999999999(少了 1); - 最终导致
j * 999999999 % n[i]的判断结果完全错误。
问题 2:浮点数无法参与整数运算
C 语言中,%(取余)、++等运算符只能用于整数,如果直接写:
1 | if ((j * pow(10, k[i])) % n[i] == 0) // 错误! |
编译器会报错 —— 因为j * pow(...)的结果是浮点数,浮点数不能用%取余。
即使你强制转换:
1 | if ((j * (int)pow(10, k[i])) % n[i] == 0) |
也会因为问题 1 的精度丢失,导致转换后的整数出错(比如(int)999999999.999=999999999)。
在函数里面声明它要输出返回的值时注意要和定义函数时的函数类型保持一致,避免类型不匹配
while不仅可以循环,还可以循环判断,
一、先搞懂:什么是质因数?
1. 基础定义(通俗版)
-
质数:只能被 1 和自身整除的数(比如 2、3、5、7、11…),是数的 “最小不可分割单位”;
-
因数:能整除某个数的数(比如 6 的因数是 1、2、3、6);
-
质因数:既是质数、又是某个数的因数(比如 6 的质因数是 2 和 3,1 和 6 不是质数,所以不算);
-
质因数分解
:把一个数拆成若干个质数相乘的形式(比如:
- 12 = 2×2×3(也写作 2²×3¹);
- 375 = 3×5×5×5(即 3¹×5³);
- 100 = 2²×5²)。
2. 核心性质
任何大于 1 的正整数,都能唯一分解为一组质因数的乘积(数学上叫 “唯一分解定理”)—— 这是质因数能解决很多算法题的根本原因。
二、质因数在代码中的常见用途
质因数分解是算法题的 “万能工具” 之一,核心用途集中在数论类问题,比如:
| 应用场景 | 举例(对应本题 / 同类题) | 代码作用 |
|---|---|---|
| 判断整除性 | 本题 “结果能被 n 整除”、判断 a 是否是 b 的倍数 | 对比质因数的种类和个数,无需暴力取余 |
| 找末尾 0 的个数 | 本题 “末尾 k 个 0”、统计 n!(n 的阶乘)末尾 0 的个数 | 统计 2 和 5 的质因数对数(10=2×5) |
| 求最大公约数(GCD) | 求两个数的最大公约数、最小公倍数(LCM) | 取质因数的最小 / 最大个数相乘 |
| 判质数 / 合数 | 判断一个数是否是质数、分解大数 | 质因数分解后,若只有 1 个质因数则是质数 |
| 找最小 / 最大满足条件的数 | 本题 “找最小满足条件的数”、找 n 的最小倍数 | 补充 / 删减质因数,保证 “最小” 且满足条件 |
三、拆解关键句:“能被 n 整除” 的数学本质
这句话是理解本题的核心,我们用 “人话 + 例子” 讲透:
1. 核心翻译
“结果能被 n 整除” → 结果的质因数分解式中,必须包含 n 的所有质因数,且每个质因数的个数≥n 中的个数。
2. 举例验证(结合本题)
比如 n=375(质因数分解:3¹×5³),要让某个数 ans 能被 375 整除:
-
ans 的质因数必须包含 “3” 和 “5”(少一个都不行);
-
ans 中 3 的个数≥1,5 的个数≥3(个数不够就不行);
比如 ans=30000(分解:2⁴×3¹×5⁴):
-
包含 3(个数 1≥1)、5(个数 4≥3)→ 能被 375 整除;
比如 ans=10000(分解:2⁴×5⁴):
-
缺少质因数 3 → 不能被 375 整除;
比如 ans=75(分解:3¹×5²):
-
5 的个数 2<3 → 不能被 375 整除。
-
再比如 n=12(2²×3¹):
- ans=24(2³×3¹):包含 2(3≥2)、3(1≥1)→ 能被 12 整除;
- ans=18(2¹×3²):2 的个数 1<2 → 不能被 12 整除。
总结
- 质因数是 “数的最小构成单位”,分解后能把 “整除、倍数、末尾 0” 等问题转化为 “质因数个数的加减”,避开暴力循环;
- “能被 n 整除” 的本质是 “质因数的包含关系”—— 这也是本题能从 O (T×n) 暴力法优化到 O (T×logn) 的核心;
- 代码中统计质因数个数,就是为了量化这种 “包含关系”,进而计算需要补充的质因数数量,最终得到最小解。
简单说:质因数分解把 “数的运算” 降维成 “质因数个数的运算”,是数论题从 “暴力” 到 “高效” 的关键桥梁。
常见模板
输入格式转换(字符串转数字)
1 | // 题目:读取一个字符串形式的数字,计算其各位和 |
核心计算模块:题目的 “灵魂”,可封装复用
核心目标:
- 实现题目的核心逻辑,尽量封装为独立函数(复用性强);
- 专注于 “计算逻辑”,不掺杂输入输出、边界判断。
示例 1:排序题(核心:冒泡排序)
1 | // 核心计算模块:冒泡排序函数(可复用) |
示例 2:数论题(核心:质因数分解)
1 | // 核心计算模块:质因数分解函数(可复用) |
示例 3:动态规划(核心:状态转移)
1 | // 核心计算模块:背包问题(01背包) |
4. 结果生成模块:加工核心结果,贴合题目要求
初识二分法
1 |
|
一、前缀和的核心定义
前缀和(Prefix Sum)是一种预处理数组的技巧,核心是构建一个新数组 pre,其中 pre[i] 表示原数组 nums 中从第一个元素到第 i 个元素的累加和(或从 0 到 i-1 的累加和,取决于下标定义)。
简单说:前缀和数组的每一位,都是原数组前若干位的总和。
可以把结构体理解为:
把多个不同 / 相同类型的变量 “打包” 成一个新的类型,用来描述一个 “有多个属性的事物”。
比如题目中 “队伍” 有两个属性:能力值(int)、抗压值(int),用结构体就能把这两个属性绑在一起,而不用分开定义两个数组。
代码例子(定义 + 使用结构体)
1 | // 定义结构体:描述“队伍”这个事物,包含a(能力值)和k(抗压值)两个属性 |
核心特点
- 结构体是 “自定义类型”:像 int、char 一样,是一种数据类型,只是由你自己定义;
- 一个结构体变量对应 “一个完整的事物”:比如
struct Team t1就对应 “一支有能力值和抗压值的队伍”; - 访问属性用
.:结构体变量。属性名(如t1.a)。
2. 结构体数组名:结构体数组的 “名字”,本质是数组首地址
第一步:先理解 “结构体数组”
结构体数组就是:数组里的每个元素都是一个结构体变量,用来存储多个 “有复合属性的事物”(比如多支队伍)。
第二步:结构体数组名
结构体数组名就是你给这个数组起的 “名字”,比如定义struct Team t[3];,t就是这个结构体数组的名字。
代码例子(结构体数组)
1 | struct Team { |
数组的上一层就是结构体
3. 数组首地址:数组在内存中的 “起始位置”
数组首地址就是:数组第一个元素在计算机内存中的地址,可以理解为 “数组的门牌号”。
关键特性
-
数组名 “等价于” 数组首地址:比如结构体数组
t,t本身就代表t[0]的地址(首地址); -
地址的表示:用
&取地址符可以获取,比如&t[0]就是t[0]的地址,和t完全相等; -
内存视角(以
1
t[3]
为例):
内存地址(示例) 对应元素 0x1000(首地址) t[0] 0x1008 t[1] 0x1016 t[2]
代码验证(数组名 = 首地址)
1 | struct Team { |
4. 比较函数指针:指向 “比较函数” 的地址,给 qsort 传 “函数入口”
第一步:先理解 “函数指针”
函数指针就是:存储函数在内存中起始地址的变量,可以理解为 “函数的门牌号”—— 通过这个指针,就能找到并调用对应的函数。
第二步:qsort 需要的 “比较函数指针”
qsort 不知道你想怎么比较两个元素(比如按a+k升序、按a降序),所以需要你把 “比较函数的地址” 传给它,qsort 拿到这个地址后,就能调用你的比较函数,知道怎么排序。
代码例子(比较函数 + 函数指针)
1 |
|
return (tx->a + tx->k) - (ty->a + ty->k);是什么意思
一、先拆解每一部分的含义
| 代码片段 | 含义 |
|---|---|
tx->a |
指针 tx 指向的队伍的能力值(a 对应题目中的 Ai) |
tx->k |
指针 tx 指向的队伍的抗压值(k 对应题目中的 Ki) |
tx->a + tx->k |
计算第一个队伍的 a+k 总和(本题的排序依据) |
ty->a + ty->k |
计算第二个队伍的 a+k 总和 |
(tx->a+tx->k) - (ty->a+ty->k) |
两个队伍的 a+k 做差,返回差值(决定排序顺序) |
二、核心规则:返回值决定排序顺序
qsort 的比较函数通过返回值的正负 / 零判断两个元素的大小关系,规则是:
| 返回值 | 含义 | 排序结果 |
|---|---|---|
| < 0(负数) | 第一个元素(tx)更小 | tx 排在 ty 前面 |
| = 0(零) | 两个元素相等 | 顺序不变 |
| > 0(正数) | 第一个元素(tx)更大 | ty 排在 tx 前面 |
因此,return (tx->a+tx->k) - (ty->a+ty->k) 的本质是:
按两个队伍的 a+k 总和进行「升序排序」(a+k 小的队伍排在前面)。
三、举例子理解(结合题目样例)
题目样例中有 3 支队伍:
- 队伍 1:
a=10, k=3→a+k=13 - 队伍 2:
a=2, k=5→a+k=7 - 队伍 3:
a=3, k=3→a+k=6
我们拿队伍 1(tx)和队伍 3(ty) 举例:
1 | // tx指向队伍1,ty指向队伍3 |
返回值是正数 → qsort 判定「tx(队伍 1)更大」→ 把 ty(队伍 3)排在 tx(队伍 1)前面。
再拿队伍 3(tx)和队伍 2(ty) 举例:
1 | // tx指向队伍3,ty指向队伍2 |
返回值是负数 → qsort 判定「tx(队伍 3)更小」→ 把 tx(队伍 3)排在 ty(队伍 2)前面。
最终排序结果:队伍 3(6)→ 队伍 2(7)→ 队伍 1(13),和题目样例的最优顺序一致。
四、反向验证(如果想降序怎么办?)
如果把代码改成:
1 | return (ty->a + ty->k) - (tx->a + tx->k); |
就是按 a+k 降序排序(大的排在前面),比如样例中会排成:队伍 1→队伍 2→队伍 3,这会导致最大压力指数变成 9(而非样例的 2),验证了升序才是最优解。
核心要点
- 函数名就是函数指针:比如
cmp本身就是这个比较函数的地址,直接传给qsort即可; qsort的第四个参数类型是int (*compar)(const void *, const void *):这是 “函数指针类型”,要求你传的函数必须符合这个格式(参数是两个const void*,返回 int)。
二、把所有概念串起来(结合 qsort 的调用)
再看qsort的调用代码:
1 | qsort(t, 3, sizeof(struct Team), cmp); |
t:结构体数组名 → 等价于数组首地址(告诉 qsort 要排序的数组从内存哪个位置开始);3:数组元素个数(告诉 qsort 要排多少个元素);sizeof(struct Team):单个结构体元素的大小(告诉 qsort 每个元素占多少内存,方便遍历);cmp:比较函数指针(告诉 qsort “怎么比大小” 的函数入口地址)。
三、总结
| 概念 | 大白话理解 | 例子 |
|---|---|---|
| 结构体 | 打包多个属性的自定义数据类型 | struct Team {int a; int k;} |
| 结构体数组名 | 给结构体数组起的名字 | struct Team t[3]; 中的t |
| 数组首地址 | 数组第一个元素的内存地址,= 数组名 | t 就是t[0]的地址 |
| 比较函数指针 | 存储比较函数地址的 “指针”,= 函数名 | cmp 就是比较函数的地址 |
这些概念本质是为了让qsort能找到 “要排序的数组”、“要排多少个元素”、“每个元素多大”、“怎么比较元素”,最终实现结构体数组的排序 —— 核心逻辑还是服务于 “按 a+k 升序排序,最小化最大压力指数” 这个目标。
对基本类型相同的指针变量,各运算符的合法性如下:
- 选项 A(+):指针变量不能直接做加法运算(无意义,会导致指向非法内存),因此该运算符不可用。
- 选项 B(-):同类型指针相减,表示两个指针之间的元素个数(如数组中两个指针相减,结果是元素间隔数),合法。
- 选项 C(=):指针变量可以赋值(如将一个指针指向另一个变量的地址),合法。
- 选项 D(==):同类型指针可以比较是否指向同一内存地址,合法。
指针减法有两个关键限制(必须满足才合法):
- 两个指针必须是 同类型(如都是
int*、都是char*,不能int* - char*); - 两个指针必须 指向同一连续内存块(如同一数组、同一动态分配的内存),否则结果无意义(语法允许但逻辑错误)。
指针变量存储的是 “格子的编号”(内存地址),而 指针相减的结果 = (两个指针的地址差)÷(单个格子的大小),最终得到的是 “两个指针之间隔了多少个格子”—— 也就是 “元素个数(间隔数)”。
数组的核心特性是 “元素在内存中连续存储”,因此数组名(本质是数组首元素的地址)和指向数组元素的指针,是指针减法的典型用法。
例子 1:int 数组(每个元素占 4 字节)
1 |
|
- 结果解读:
p2 - p1 = 3,表示p1(指向 arr [0])和p2(指向 arr [3])之间隔了 3 个 int 元素(arr [1]、arr [2]、arr [3] 与 arr [0] 的间隔数是 3)。 - 注意:结果是 “元素索引差”(3 - 0 = 3),和数组元素的索引完全对应,这就是指针减法的实用价值 —— 快速计算两个元素在数组中的位置间隔。
例子 2:char 数组(每个元素占 1 字节)
1 |
|
- 实参:调用函数时传入的 “原始变量 / 值”(比如本题
main中的&y和x); - 形参:函数定义时接收实参的 “局部变量”(比如本题
fun中的*x和y)。
C 语言中,普通变量作为参数时,采用 “值传递” —— 相当于把实参的 “值” 复制了一份,交给形参。函数内部操作的是这份 “副本”,和原始变量(实参)没有直接关联。
也就是说函数里面普通变量要想改变必须用return返回,如果是void类型函数里面改变或赋值某个普通变量的动作是无效的,但是对于指针有效
冒泡排序的核心是 “相邻元素比较,逆序则交换”,每一趟会将当前未排序部分的最大元素 “冒泡” 到末尾。对初始序列4,5,6,3,2,1(从小到大排序),每一趟的过程如下:
第 1 趟排序(目标:将最大的 6 移到末尾)
- 比较并交换:
4,5,3,6,2,1→4,5,3,2,6,1→4,5,3,2,1,6 - 结果:
4,5,3,2,1,6(末尾 6 已排好)
第 2 趟排序(目标:将次大的 5 移到倒数第 2 位)
- 比较并交换:
4,3,5,2,1,6→4,3,2,5,1,6→4,3,2,1,5,6 - 结果:
4,3,2,1,5,6(末尾 5、6 已排好)
第 3 趟排序(目标:将第三大的 4 移到倒数第 3 位)
- 比较并交换:
3,4,2,1,5,6→3,2,4,1,5,6→3,2,1,4,5,6 - 结果:
4,3,2,1,5,6(实际过程中,第 3 趟未完成 4 的移动,最终状态为4,3,2,1,5,6)
1 |
|
数组名a是数组首元素的地址(a等价于&a[0]);不是整个数组
-
*p = a+3表示指针p指向a[3](因为a+3是a[3]的地址); -
指针的下标运算
1
p[2]
等价于
1
*(p+2)
p指向a[3],p+2表示指针向后移动 2 个int元素,指向a[3+2] = a[5];*(p+2)即a[5]的值,为5。
首先明确结构体指针的访问规则:
- 结构体指针
p访问成员时,应使用->(格式:p->成员名); - 取成员地址时,需在成员前加
&(格式:&p->成员名或&(*p).成员名)。
1 | &(*p).age` 等价于 `&p->age |
->高于++,u不加括号的时候先指向再增加指针位置,如果把++p括起来就是指针向前移动一位然后指向原值
-> > * > ++
px 是「指向struct stu结构体的指针」,它的本质是存储了结构体变量x的内存地址;
*px 是「解引用px」,意思是:顺着px里存的地址,找到它指向的那个结构体变量x本身。
struct stu x是一套房子这个实体,地址内容是&x,struct stu*是存储了地址的纸条,
*px是拿着纸条,找到了房子这个实体
1 | // 定义结构体类型 |
sizeof(struct ps):计算结构体ps的大小,需要内存对齐,编译器中默认按 成员中最大基本类型的字节数 对齐
比如double i:占8字节
char arr[24]:占24字节
总大小32,刚好是8的整数倍,无需额外补位
内存对齐的规则:成员对齐,整体对齐
Denominator:分母
++i(前置自增):先自增,再返回新值。
i++(后置自增):先返回当前值,再自增。
break
不仅可用于 switch,还可用于 for, while, do-while 循环中,用于提前跳出当前循环。
continue 语句的作用是使程序的执行流程跳过包含它的所有循环
continue 的作用是:跳过本次循环剩余语句,直接进入下一次循环判断。
它不会跳出所有循环,只影响最内层的当前循环。
1 | for (int i = 0; i < 5; i++) { |

do-while 是先执行一次循环体,再判断条件。
只要 while 中的条件为真(非零),就继续循环。
num++是将num的值加1
关闭和打开代码ai

点击设置搜索Copilot

点击功能里面的chat

点击*对应的true → 改为false
*** 每输出5个数就换行(第5,10,15,…项后换行)
1 | if (i % 5 == 0) { |
*** 斐波那契
用两项滚动计算
1 | current = f1 + f2; // 新的一项 = 前两项之和 |
保证输出结果在长整型范围内就用long
if的小括号判断语句里面必须是两个等号
条件判断错误:if(m>=1 && m<=n && n<=500)
题目要求:1 ≤ M ≤ N ≤ 500
但你写成了 m <= n,而输入习惯是 M N(先 M 后 N),所以应判断 M <= N
正确条件:if (m >= 1 && n <= 500 && m <= n)
代码书写的正确步骤
1.输入的合法性判断(用if语句)
2.初始化变量(让变量从零或x开始递归)
这时开始思考实现算法的步骤,确定变量的个数,明确要用的实现方法
3.循环前n项
(1).开始计算当前项的值
(2).打印当前项
(3).控制换行(每x项为一行)
(4).更新循环变量
4.补充末尾换行
编程可以往最笨的方向上想————遍历爆破
但是遍历的次数越少与好
(!found) 等价于 found == 0

明确循环,细化步骤
第一个循环是遍历边界数值,这是随n变化的最直观表现,因为已知边界,所以用for循环
第二个循环是拆分步骤,因为拆分数字是随n变化的,并且每一步骤相似度较高,都是取出位数,算出位数的三次方,去掉位数,进入下一位,所以也是循环
模板步骤
1.取当前最后一位数字 digit = temp % 10;
2.累加“数字的n次幂” sum += pow(digit, n);先判断步骤里面有没有“加”,然后判断是不是累加,即随着n的变化而改变加的个数
3.去掉最后一位,继续处理 temp = temp / 10;
这是while循环里的三步,主要思考每一小循环里的具体步骤,可以用假设法,假设n=5(这时你已经写完了scanf,所以确定n为一个定值)当写出用m遍历所有5位数后,确定m为一个定值,m=12345,如果人脑直接想就一次性拆分成四个数字了,但是编码需要顺序,从右往左按顺序一小步一小步遍历,所以从个位数字开始提取(第1,3步),然后看到条件等式想要写if判断,但是没有确定的右边界,也就是说判断也是随着n的变化而变化的,判断不是一个定值而是变量,所以把if转换为while+if,用中间值sum来连接,之前想写if(个位数1+十位数2+百位数^3+……i),现在直接令sum+=pow(位数,n次幂),然后if判断suni
沙漏
各行符号中心对齐
给定任意N个符号,不一定能正好组成一个沙漏。要求打印出的沙漏能用掉尽可能多的符号。
char ch; 是 C 语言中的变量声明语句,核心解析如下:
类型说明:char 是字符类型关键字,用于声明存储单个字符(如 *、#、字母、数字等)的变量,在内存中占 1 字节(8 位);
1.计算行数,设最大行数行数为k,用while计算,k与n的关系是求和公式,则将n分为沙漏的上半部分和下半部分,假设k刚好满足,则n=2*\kk-1,然后k++开始计算,注意循环跳出后要回退k–
然后计算剩余符号数,remain=n-2\k*k+1
2.开始打印,有上下两部分组成,因为上下两部分的循环改变方向,所以用两个循环
有空格,符号,换行三部分组成
在git中发送密钥时关闭代理,初始化时打开代理
ctrl+f 打开搜索
localhost:0.0.0.0
将本地blog部署到线上
1 | deploy: |
注意冒号后面的空格键
2.安装部署插件并生成 + 部署
打开git bash here,进入本地 Hexo 博客根目录,执行以下命令:
安装 Git 部署插件:
1 | npm install hexo-deployer-git --save |
清理缓存、生成静态文件、部署到 GitHub:
1 | hexo clean |
最终还是没解决
网页源码看查看器和网络
看不出来就用dirsearch
python3 dirsearch.py -u http://ip地址
查看有没有git文件
有的话用wget -r 地址下载文件
然后用git log查看的时候要注意跳转到下载的文件里,cd ~/ip地址
弱比较,自动转换成相同类型
结构体 学生成绩排序
1 |
|
递归函数接口
1 | double fact(int n){ |
Ackermenn函数
1 | int Ack(int m,int n){ |
思考步骤
- 明确函数功能
- 写出基本情况:用来决定终止条件(分类讨论)
- 写出递归情况:不满足基本情况时,让函数调用自己(重点是找下一步的等价关系式,找这个关系式的关键是先假设这个函数已经构建好,可以直接帮我们得到正确答案了)
数组传参会退化为指向首元素的指针,丢失长度信息
多维数组最外层数组名(行数)会退化为指针,内层维度的长度必须显式指定arr[][3]
%zu是sizeof返回值的占位符:size_t c=sizeof(*p);printf("%zu",c);
IntArray arr={{1,2,3},3}相当于int a
1 | // 以下 3 种写法完全等价,均被解析为 int* arr |
定义一个字符串数组时要初始化全为零char path[10] = {0};,使其可以稳定输出
因为在内存层面,数字0与\0的二进制表达式(ascll码值)完全相同,所以初始化为全0,等价为全‘\0’
%s是字符串的占位符
递归输出全排列
1 |
|
设计递归函数方法
- 明确递归目标(也就是函数接收的参数的个数和功能)
- 确定递归终止条件(一般是 函数接收参数==边界值)
- 开始递归:遍历,if判断,标记,记录,递归,回溯
链表
头指针存放的是头结点的地址而非内容
单向链表的结点也可以用静态内存分配
通常使用结构的嵌套来定义单向链表结点的数据类型。
数组支持随机访问(通过下标直接定位),查询更高效;链表需从头结点依次遍历,查询效率更低
结构图的声明后面加变量名[数字]相当于声明了两个数组对象struct 结构体名 变量名[数字]
struct Person *friend;说明这个指针只能指向其他Person类的对象
如果结构体成员里面有n
friend->n等价于(*friend).n,表示friend所指向的结构体的成员n
只有指针类型的变量才能用->
malloc(n)动态内存分配函数,在堆上分配n字节未初始化内存,返回void*指针
也就是说malloc返回了一个在堆上的内存地址
malloc只分配空间,不清理内容
未初始化是指内存里的随机垃圾值
这样想象内存结构
1 | p → [num: ?, next: ?] @0x1000 |
内存对齐是指:某些类型的数据必须存储在特定地址的倍数位置上
int *p[3]表示声明一个指针数组,包含三个元素,每个元素都是int *类型
数组名不能被赋值
*p = &a;一般编译错误,但是题目出了就是指针指向a[0]
++p和p++是对指针p本身做自增(移动地址)
++(\*p)和(\*p)++是对p指向的值做自增(修改内容)
一级指针无法修改调用者指针的值,所以让二级指针修改指针让其指向一个新的值
二级指针是一个指向指针的指针
1 | int a = 10; |
求一个数的因子=>找到能整除的数
1 | char *language[] = {"FORTRAN", "BASIC", "PASCAL", "JAVA", "C"}; |
language[1]的值是一个地址,该地址存放的是字符‘B’
链表
1 | void input(){ |
文件
从文件的逻辑结构上看,c语言把文件看作数据流,并将数据按顺序以一维方式组织存储。
c语言源程序是文本文件,目标文件和可执行文件是二进制文件。
对于缓冲文件系统,在进行文件操作时,系统自动为每一个文件分配一块文件内存缓冲区(内存单元)。
文件指针指向一个 FILE 结构体,该结构体内部维护着当前文件位置和缓冲区信息。
fopen()打开文件失败,返回值是NULL
函数fopen中第一个参数的正确格式是"c:\\user\\text.txt"
第二次机考练习
指针拆分小数和整数**“要修改变量,传地址(&);函数内用 \* 赋值”**
1 |
|
1 | #include<stdio.h> |
选择法排序
选择排序思想:
每一轮从未排序部分中找出最小值,放到已排序部分的末尾
1 |
|
1 |
|
1 |
|
1 | #include<stdio.h> |
如有错误,多多指教