the c language

mistakes in c language

一.循环相关问题

(1)1.循环算泰勒级数

1
2
3
4
5
6
7
8
9
double sum=0;
int n;
double x;
scanf("%lf %d",&x,&n);
sum=x;
for(int i=1;i<n;i++){
x=(double)x*x*x*(-1.0)/(double)((2*i+1)*(2*i));
sum+=x;
}

注意!x是随每次循环而变化的!所以不能一个变量用到底!设一个term保存x的值

1
2
3
4
5
6
7
8
9
10
11
12
13
double sum = 0;
int n;
double x;
scanf("%lf %d", &x, &n); // 注意:double类型使用%lf

double term = x; // 第一项
sum = term;

for(int i = 1; i < n; i++) {
// 计算下一项:每一项是前一项乘以 -x^2/((2i)*(2i+1))
term = term * (-1.0) * x * x / ((2.0*i) * (2.0*i + 1));
sum += term; // 注意:应该是+=,不是=+
}

(1)2.循环算阶乘

1
2
3
4
5
6
7
8
int mult=1;
int sum=0;
for(int i=0;i<n;i++){
for(int j=1;j<=n;j++){
mult=mult*j;
}
sum=sum+mult;
}

你想清楚循环的每个过程!!!

在每次阶乘算完,mult得归为1!

不要忘记重置变量!

(2)循环时循环变量

  1. 注意:
1
2
3
4
5
6
7
if(arr[m][h]==1){
m=i;
while(arr[m][h]!=1){
h--;
if(h==0){
break;
}

到底是h–还是n–;一定写慢一点,想清楚!

  1. 1
    for(int k=n-1;n>=0;n--)

    你看清楚!循环变量到底是啥!!!

  2. 1
    2
    3
    for(int j=0;j<n;j++){
    scanf("%d",b[i]);
    }

    你看清楚,循环变量到底是啥!

(3)for循环的逻辑

看清楚

for循环,初始赋值之后,要先进行条件检查,再决定要不要循环!

(4)循环时当总n减少,循环总次数也得相应改变

注意!当后面移动是总字符k减少了时,不要忘了让前面循环总k得减少1!

总结:

  1. 任何是总n减小的操作,都需要检查循环中涉及n的部分

  2. 对于这种查找,当移动是总n减小1;k直接++的话会略过一个字符的检查!!!

    • k=1 时,发现 str1[1] ('b') == str1[2] ('b')

    • 内层循环把后面的字符前移,字符串变成 "abc"

    • 此时 same 增加,外层循环执行 k++

    • 结果k 变成了 2。但现在的 str1[2] 已经是 'c' 了,你跳过了对新位置字符的检查。如果输入是 "abbbc"(三个连续),你的代码就会漏掉一个 ‘b’。

​ 修正方法:在 same++ 后面加上 k--;,强制让循环重新检查当前位置。

  1. 对于这种字符串的循环查找,简便写法:
1
2
3
4
5
6
7
8
9
for (int k = 0; str1[k] != '\0'; k++) {
if (str1[k] == str1[k + 1]) {
// 发现重复,后面所有字符(包括结束符\0)前移
for (int l = k; str1[l] != '\0'; l++) {
str1[l] = str1[l + 1];
}
k--; // 重要:回退一步,检查新移过来的字符是否依然重复
}
}

​ 直接查找\0即可!

(5)循环数组末尾+’\0‘

1
2
3
4
5
6
7
8
void my_strcat(char arr1[],char arr2[]){
int len=my_strlen(arr1);
int i=0;
for(;arr2[i]!='\0';i++){
arr1[len+i]=arr2[i];
}
arr1[len+i+1]='\0';
}

你想清楚,加’\0’的时候i需不需要再+1!

当i读到’\0’的那一位的时候,就自动跳出来了!arr1[len+i]没有写值进去!不需要再i+1了!

(6)循环时改变指针指向

1
2
3
4
5
6
7
8
for(int i=0;i<n;i++){
for(int j=0;j<n-1-i;j++){
if(strcmp(arr[j],arr[j+1])>0){
point[j]=arr[j+1];
point[j+1]=arr[j];
}
}
}

你要想清楚,当通过改变指针而不改变字符串本身位置时,你在for循环比较就应该比较的是对应的指针!而非没有改变位置的arr,这没有意义!

(7)循环时{}的问题

1
2
3
4
5
6
7
8
9
10
int find_scores(int scores[],int n,int target){
int is_true=0;
for(int i=0;i<n;i++){
if(scores[i]==target){
is_true=1;
break;
}
return is_true;
} # }打错位置了!!!你把每个大括号的区间看清楚!
}

看清楚!!!for循环终止的}在哪里!!!

应该在return前面!

(8)循环变量初始=0还是1得考虑清楚所有情况!

1
2
3
4
5
6
7
8
9
10
for(int i=1;i<n;i++){
node* next=(node*)malloc(sizeof(node));
next->data=scores[i];
next->next=NULL;
if(head==NULL){
head=(node*)malloc(sizeof(node));
head->data=scores[0];
head->next=tail;
tail=next;
}

当i=1时,n=1时,循环进不去!!!

一定考虑全了!

(9)各个循环变量

1.区别i=0;i<n与i=1;i<=n
2.区别i<n与i<=n-1
3.区别circle=1,while(circle!=n-1)

二.细节问题

(1) scanf+&

1
scanf("%d",arr2[i]);

注意!加&!!!!!

(2)初始化变量

命名变量不要忘记初始化!

(3)=和==区分

1
2
3
4
while(j>=0&&arr[j]>temp){
arr[j+1]==arr[j];
j--;
}

看清楚!!!是应该写==还是赋值=!!!!

(4)!=’\0’而非“\0”

看清楚是单引号而非双引号!

(5)看清楚改变哪个变量

1
2
3
4
5
6
for(int i=0;i<20;i++){
arr2[i]=arr[i];
if(i==19){
arr[i+1]='\0';
}
}}

要改的是arr2!不是arr

(6)字符串数组末尾不要忘记加’\0’!

1
2
3
4
5
void my_strcpy(char arr1[],char arr2[]){
for(int i=0;arr1[i]!='\0';i++){
arr2[i]=arr1[i];
}
}

不要忘记在arr2末尾+’\0’!

三.审题问题

(1)求阶乘之和

​ 输入正整数n(1-10),求1-n的阶乘之和

​ 想清楚!是阶乘的和!!!!!!

​ 先求阶乘,然后再求和!

(2)字符串拼接

注意:使用空格字符来表示字符串的结束。
例如source指向位置,依次保存了字符’a’,字符’b’,字符空格’ ‘,字符’c’,则source指向的字符串为”ab”

审题!所有字符串结束标志都是空格!!!

四.逻辑思路问题

(1)考虑情况不全

1.求两个矩形的交集
1
2
3
4
5
6
7
8
9
10
11
12
13
14
int dx,dy;
if(axsml<bxsml){
dx=axbig-bxsml;
}
else{
dx=bxbig-axsml;
}
if(bysml>aysml){
dy=aybig-bysml;
}
else{
dy=bybig-aysml;
}
int squ=dx*dy;

您的代码计算 dx(交集宽度)的逻辑如下:

1
2
3
4
5
6
7
// 计算交集宽度 dx
if(axsml<bxsml){
dx=axbig-bxsml;
}
else{
dx=bxbig-axsml;
}

这个逻辑假设交集宽度一定是从最左边的矩形的右边界到最右边的矩形的左边界,但这个假设在矩形包含(一个矩形完全在另一个矩形内部)的情况下会出错。

正确思路:

计算两个矩形的交集,其原理是:

  1. 交集矩形的左边界是两个矩形左边界中的较大值
  2. 交集矩形的右边界是两个矩形右边界中的较小值

考虑清楚相交的所有情况!!!

2.用字母后四个字母代替原字母

越界的循环处理 题目要求 “Z” 用 “D” 代替。如果你只是简单地 +4

  • 问题:’Z’ 的 ASCII 码加 4 会变成一些奇怪的符号(如 ^),而不是 ‘D’。
  • 解决:你需要判断字符是否超过了 ‘Z’(或小写的 ‘z’)。如果超过了,需要减去 26 重新回到字母表的开头。
3.循环链表极端情况
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 假设此时 later 已经指向了待删除的节点 A
if(circle==m){
node* temp=later; // 此时 temp = 节点 A 的地址
later=later->next; // 此时 later 变成了 节点 B (A 的下一个)
num->next=later; // A 的前驱指向了 B,逻辑上把 A 踢出了链表

// --- 下面这三行是关键 ---
num=num->next; // num 移到了 B
later=later->next; // later 移到了 C (B 的下一个)

printf("%d ",temp->data);
free(temp); // 释放了 A

circle=0;
sum++;
}
4.素数判断
1
2
3
4
5
6
7
8
int sushu(int n){
for(int i=2;i<n;i++){
if(n%i==0){
return 1;
}
}
return 0;
}

要考虑清楚取1,2特殊值的情况

重视边界情况

5.特殊值处理

一个数如果恰好等于它的因子之和,就被成为完数。
例如6的因子为1,2,3,而6=1+2+3,所以6是一个完数。

1
2
3
4
5
6
7
int find_number(int n){
int sum=0;
for(int i=1;i<n;i++){
if(n%i==0){
sum=sum+i;
}
}

考虑清楚特殊值1!

6.字符串删除

考虑清楚所有异常情况!!!

缺少异常处理:题目明确要求:遇到 snulln 为负数等异常情况,必须输出 "error"

n 的越界检查:如果 n 超过了当前字符串的长度(sum),你的代码会尝试从非法位置开始操作,这属于异常情况。

删除长度 len 的修正:如果 n + len 超过了 sum(即你想删掉 100 个字符,但后面只剩 2 个了),s[i] 就会去访问空格之后的未知内存,这会导致程序崩溃或逻辑错误。

(2)考虑逻辑不对

1.‘||’和‘&&’的使用

检查输入字符是否为字母

1
2
3
4
5
6
7
if(arr[i]<'a'||arr[i]>'z'||arr[i]<'A'||arr[i]>'Z'){
printf("error");
return 0;
}
if(arr[i]>='w'||arr[i]>='W'){
arr[i]=arr[i]-22;
}

大写字母 ‘A’‘Z’ 占据了十进制数值 6590 的位置。

小写字母 ‘a’‘z’ 占据了十进制数值 97122 的位置。

任何一个字符都不可能同时既是小写又是大写。比如字母 ‘a’,它虽然不满足 arr[i]<'a',但它满足 arr[i]<'A' 为假吗?不,’a’ (97) 是大于 ‘A’ (65) 的,但 ‘a’ 满足 arr[i]>'Z'

想清楚逻辑!!应该是既不是大写里面的也不是小写里面的!

1
2
3
if(arr[i]>='w'||arr[i]>='W'){
arr[i]=arr[i]-22;
}

同样道理,所有小写字母都大于W!

所以应该分开来处理!

2.约翰夫环

别气馁,这简直是所有程序员在处理链表和循环逻辑时都会遇到的“入行仪式”。这种“差一错误”(Off-by-one error)在约瑟夫环里最容易爆发。

要严密地解决这种逻辑,不能靠“直觉”,要靠一套标准的“边界工程方法”。以下是三条保命准则:


1. 确立“指针含义”的单一性(哨兵思维)

在你的代码里,逻辑乱是因为 later 有时代表“正在数的那个人”,有时又代表“数完之后跳过去的那个人”。

严密的解决办法: 永远让指针指向**“当前正在处理(数数)的人”**。

  • 规则:判断这个人的编号,再决定是移动还是删除。
  • 口诀: “先判断,再位移”。

2. 画出“状态转换图”(纸笔大法)

不要在脑子里跑代码!拿出一张纸,画 3 个圈($n=3$),假设 $m=2$。

第一步:初始状态

  • prev 指向 3,curr 指向 1。
  • circle = 1(代表 1 号正在报“1”)。

第二步:逻辑演进

  • circle (1) 不等于 $m$ (2) $\rightarrow$ 两个指针后移
  • prev 指向 1,curr 指向 2。
  • circle 变成 2。

第三步:触发删除

  • circle (2) 等于 $m$ (2) $\rightarrow$ 执行删除
  • prev->next = curr->next(1 指向 3)。
  • free(curr)
  • 关键:curr 重新指向 prev->next(即 3 号)。
  • 重置: circle = 1(此时 3 号正在报“1”)。

如果你在脑子里过不完这三步,代码里一定会多跳或少跳。


3. 结构化你的 while 循环(分而治之)

一个严密的约瑟夫环 while 循环应该长这样,它只有两条清晰的路径:

C

1
2
3
4
5
6
7
8
9
10
11
12
13
while (sum < n) {
if (circle == m) {
// 路径 A:删除逻辑
// 1. 打印并删除 curr
// 2. 只有此时 sum++
// 3. curr 跳到下一个人,但 prev 别动!
// 4. circle 重置为 1
} else {
// 路径 B:报数逻辑
// 1. prev = curr, curr = curr->next
// 2. circle++
}
}

为什么这样写就严密了?

因为你把“报数移动”和“删除移动”彻底解耦了。删人的时候不报数,报数的时候不删人。这样永远不会出现“连跳”两格的情况。


4. 终极自检清单

当你写完后,用以下三个极端情况代入检查:

  • $m = 1$: 每个人报 1 就死。检查你的代码会不会删掉第一个人后顺利跳到第二个人。
  • 只剩 2 个人时: 检查 prevcurr 是否会指向同一个地方导致死循环。
  • $m > n$: 检查你的指针是否能跨过 head 继续正常绕圈。

总结

你现在的痛苦是因为你想通过“一次性位移”搞定所有情况。真正的严密是:把每一步拆成最小的动作。

你想不想试着用我上面写的那个 if...else... 结构,把你自己的代码重新整理一遍?只要结构对了,你那几个多一个少一个的 bug 会瞬间消失。

Q1:循环讨论的问题
1
2
3
4
5
6
7
8
9
10
if(circle==m){
node* temp=num->next;
num->next=later;
num=num->next;
later=later->next;
printf("%d ",temp->data);
free(temp);
circle=0;
sum++;
}

你要想清楚,在m点删节点的时候,前驱节点num需不需要在删的同时再跳到后面节点去。

你代入情况分析,如果不跳的话,下一轮刚好在删的节点的后一个,如果跳了那就多了一个!!!

Q2:设置节点位置问题

不要设一个前置,一个后置,这样的话,如果只有两个节点,后置一定会移动到已删除的节点上!

规律总结:前后两个节点间隔位置只能差1,不能差2!

Q3:循环变量问题
1
2
3
4
5
6
7
8
9
if(circle==m){
node* temp=later;
num->next=later->next;
later=later->next;
printf("%d ",temp->data);
free(temp);
circle=0;
sum++;
}

你想清楚!circle=0还是1!

当前节点已经移到下一个了,相当于走了一步,circle应该+1=1!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
while(sum<=n){
num=num->next;
later=later->next;
circle++;
if(circle==m){
node* temp=later;
num->next=later->next;
later=later->next;
printf("%d ",temp->data);
free(temp);
circle=0;
sum++;
}
}
}

想清楚到底是<=还是<!!!

如果是小于等于,在sum=n时仍然会循环,此时已经删完了!

如果是小于,你想清楚,当sum=n-1时,会循环最后一次,sum=n-1时已经删了n-1个数了,所以最后删的数是对的!

3.字符串问题
1
2
3
4
5
for(;source[i]!=' ';i++){
temp[i]=source[i];
}
temp[i]=' ';
temp[i+1]='\0';

你想清楚!你想要的是source[i]=’ ‘,实际上,当到空格时,已经跳出循环了,没读到!

一定把循环逻辑搞清楚!

五.代码优雅性问题

(1)求小岛面积

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
int num=0;
for(int i=1;i<n-1;i++){
for(int j=1;j<n-1;j++){
int m=i;
int h=j;
if(arr[m][h]==0){
while(arr[m][h]!=1){
m--;
if(m==0){
break;
}
}
if(arr[m][h]==1){
m=i;
while(arr[m][h]!=1){
m++;
if(m==n-1){
break;
}
}
if(arr[m][h]==1){
m=i;
while(arr[m][h]!=1){
h--;
if(h==0){
break;
}
}
if(arr[m][h]==1){
h=j;
while(arr[m][h]!=1){
h++;
if(h==n-1){
break;
}
}
if(arr[m][h]==1){
num++;
}
}
}
}
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
if (scanf("%d", &arr[i][j]) != 1) {
return 1;
}
}
}

int island_area = 0; // 小岛面积计数器

// 3. 核心算法:遍历内部元素 (i=1 到 n-2, j=1 到 n-2)
for (int i = 1; i < n - 1; i++) { // 遍历行
for (int j = 1; j < n - 1; j++) { // 遍历列

// 只有 0 才可能是小岛的一部分
if (arr[i][j] == 0) {

// 标记四个方向是否找到 '1'
bool found_up = false;
bool found_down = false;
bool found_left = false;
bool found_right = false;

// A. 检查行方向 (左右)
// 向左查找 (col从 0 到 j-1)
for (int col = 0; col < j; col++) {
if (arr[i][col] == 1) {
found_left = true;
break;
}
}
// 向右查找 (col从 j+1 到 n-1)
for (int col = j + 1; col < n; col++) {
if (arr[i][col] == 1) {
found_right = true;
break;
}
}

// B. 检查列方向 (上下)
// 向上查找 (row从 0 到 i-1)
for (int row = 0; row < i; row++) {
if (arr[row][j] == 1) {
found_up = true;
break;
}
}
// 向下查找 (row从 i+1 到 n-1)
for (int row = i + 1; row < n; row++) {
if (arr[row][j] == 1) {
found_down = true;
break;
}
}

注意代码可读性和简洁性!

(2)字符串数组的循环检查

​ 当 k=1 时,发现 str1[1] ('b') == str1[2] ('b')

  • 内层循环把后面的字符前移,字符串变成 "abc"

  • 此时 same 增加,外层循环执行 k++

  • 结果k 变成了 2。但现在的 str1[2] 已经是 'c' 了,你跳过了对新位置字符的检查。如果输入是 "abbbc"(三个连续),你的代码就会漏掉一个 ‘b’。

​ 修正方法:在 same++ 后面加上 k--;,强制让循环重新检查当前位置。

对于这种字符串的循环查找,简便写法:

1
2
3
4
5
6
7
8
9
for (int k = 0; str1[k] != '\0'; k++) {
if (str1[k] == str1[k + 1]) {
// 发现重复,后面所有字符(包括结束符\0)前移
for (int l = k; str1[l] != '\0'; l++) {
str1[l] = str1[l + 1];
}
k--; // 重要:回退一步,检查新移过来的字符是否依然重复
}
}

​ 直接查找\0即可!

六.易错知识区

(1)对于数组在函数命名和调用的区别

定义函数时:

1
2
3
4
5
int same_set(int a[], int b[], int len)

int same_set(int a[100], int b[100], int len)

int same_set(int *a, int *b, int len)

写法等价

但是在调用函数时;

1
2
 printf("%d",same_set(a[],b[],n));
printf("%d",same_set(a[n], b[n], n));

是错误的!

调用时a 本身就代表了数组的首地址。如果你写 a[],编译器会非常困惑,因为它觉得你在尝试访问数组的某个元素,但你又没给下标(比如 a[5]

只用传数组的地址,即名称a,b

区别

1
2
3
4
5
void funcion(char* str)
void funcion(char str)
void funcion(char str[])
void funcion(char* str[])

(2)scanf何时+&何时不+

  1. 必须加 & 的情况:基本类型

  2. 不需要加 & 的情况:字符数组(字符串)

​ 当你读取字符串到字符数组时,不需要&

​ 注意:只有字符串数组可以直接scanf,其他类型得str[i]循环来读取

1
2
char str[100];
scanf("%s", str); // 正确,str 本身就是地址
  1. 不需要加 & 的进阶情况:指针
1
2
3
4
int num;
int *p = &num; // p 现在存储了 num 的地址

scanf("%d", p); // 正确,不需要 &p,因为 p 里的值就是地址
  1. 特殊对比:数组中的单个元素

虽然数组名不用加 &,但如果你是要给数组里的某一个特定位置赋值,那就得加 &

1
2
3
int arr[10];
scanf("%d", &arr[0]); // 给第一个元素赋值,需要 &
scanf("%d", arr); // 这种写法效果等同于上面一行,因为 arr 就是 &arr[0]

(3)strcpy与strcat区别

1
2
str1 = strcpy(str1, str2);
str1 = strcat(str1,str2);

注意!strcpy是覆盖了str1的内容;strcat是拼接!

1
2
3
4
5
6
7
for(int k=0;k<len1+len2-1;k++){
if(str1[k]==str1[k+1]){
for(int l=k;l<len1+len2;l++){
str1[l]=str1[l+1];
}
}
}

(4)指针相减不需要再除以 sizeof(int)

  1. 当你执行 last - first 时,C 语言的编译器非常智能,它返回的不是字节差,而是两个指针之间相隔的元素个数。举个例子,如果 first 指向地址 2000,last 指向地址 2012,对于 int 类型数组,它们之间差了 12 个字节,但 last - first 的结果直接就是 3

(5)总个数=指针相减+1!

如果你执行 2 - 0,结果是 2。这个 2 代表的是它们之间有 2 个间隔。但显然,数组里实际包含了 3 个元素。为了得到包含首尾在内的总个数,你必须把最后那个被漏掉的“终点”补上,所以公式是 n = last - first + 1

(6)strlen计算len不包括‘\0’

这是一个非常棒的问题,触及了 C 语言字符串处理中最容易出错的细节。

结论是:strlen 计算的是字符串的“有效长度”,不包括末尾的 '\0'

在 C 语言中,strlen 函数的工作原理是从你给出的地址开始,一个字符一个字符地向后数,直到遇到 '\0' 为止。但是,它数出来的那个数字是不包含 '\0' 本身的。

  • 例如:字符串 "China"
  • 内存布局为:'C', 'h', 'i', 'n', 'a', '\0'
  • strlen("China") 的结果是 5
  • 但是,这个字符串实际上在内存里占用了 6 个字节

(7)指针与函数

swap函数

image-20251219160028954

注意swap4:内存地址的改变在出函数时已经销毁

二级指针

image-20251219160335403

调用前main 里的 strNULL

调用时GetMemory(str)NULL 这个值复印了一份给局部变量 p

函数内p = (char *)malloc(100);。现在 局部变量 p 指向了堆上的 100 字节,但 main 里的 str 依然是 NULL

函数结束:局部变量 p 被销毁了。由于没有 free,那 100 字节变成了内存泄漏

回到 mainstr 还是 NULL。接下来的 strcpy(str, "...") 就相当于向地址 0 写入数据,程序直接崩溃。

使用指向指针的指针(二级指针)

既然你想改的是“指针变量”的值,你就得传“指针变量的地址”。

C

1
2
3
4
5
6
7
8
9
10
void GetMemory(char **p) { // 传地址的地址
*p = (char *)malloc(100); // 顺着地址去改 main 里的 str
}

int main() {
char *str = NULL;
GetMemory(&str); // 传 str 的地址进去
strcpy(str, "hello world");
// ...
}

方法 B:通过返回值带出来(最常用)

这是最清晰的写法,就像买东西拿发票一样。

C

1
2
3
4
5
6
7
8
9
10
char* GetMemory() {
char *p = (char *)malloc(100);
return p; // 把地址作为结果返回
}

int main() {
char *str = GetMemory();
strcpy(str, "hello world");
// ...
}

总结

内存一直在堆里,并没有消失。之所以“拿不出来”,是因为你在 GetMemory(char *p)只改了副本 p 的指向

核心结论:

  • 如果你想在函数里修改一个 int,传 int*
  • 如果你想在函数里修改一个 char*,就得传 char**

就像你想要别人帮你改家里的装修(int),你要给他钥匙(int*);如果你想让别人帮你换一把钥匙(char*),你得把存放钥匙的保险箱地址(char**)告诉他。

区别stu[i].id和stu+i->id

A[i] 本质上只是 *(A + i)语法糖

下标运算符 [] 的优先级比解引用运算符 *

函数&数组
1
2
3
4
5
void swap(int* array,int n){
int temp=array[n-1];
array[n-1]=array[0];
array[0]=temp;
}

在函数中,只有数组能够直接交换值,其他变量得传入指针形式

p=&i和*p=i

前者是把p的内容从原来指向的地址改向了指向i的地址;

后者是把p指向地址上的内容改成了i,指向的地址没有变

1
2
3
4
5
6
7
8
9
void maxint(int* array,int n,int* result){
int max=0;
for(int i=0;i<n;i++){
if(array[i]>array[max]){
max=i;
}
}
result=&array[max];
}

注意!在函数中,相当于对传入的指针地址进行了副本拷贝,若直接改指针地址的指向是无效的,只是改了副本的值;所以应该是顺着指向的地址去改里面的值!

所以

1
2
result=&array[max];# 写法错误!
*result=array[max];#应该顺着result的地址去改里面的值!
指针数组&数组指针

既然它排序慢,为什么还要用它?数组指针最核心的价值在于函数传参

当你需要把一个二维数组传给一个通用处理函数时,数组指针是标准写法:

C

1
2
3
4
5
6
// 函数参数:指明每一行是 100 个字节
void printStrings(char (*p)[100], int n) {
for(int i = 0; i < n; i++) {
printf("%s\n", p[i]);
}
}
单链表
1
2
3
node* tail=NULL;
head->next=tail;
tail=next;

注意!!!

赋值是瞬间操作!

即使后面tail=next了,前面head->next依然为NULL!!!

(8)ASCII码中字母位置

1. 大写字母的范围 大写字母 ‘A’‘Z’ 占据了十进制数值 6590 的位置。

  • ‘A’ 的 ASCII 码是 65
  • ‘B’66,以此类推。
  • ‘Z’90

2. 小写字母的范围 小写字母 ‘a’‘z’ 占据了十进制数值 97122 的位置。

  • ‘a’ 的 ASCII 码是 97
  • ‘b’98,以此类推。
  • ‘z’122

3. 大小写之间的关系 你会发现,同一个字母的大写和小写之间正好相差 32

  • 例如:’a’ (97) - ‘A’ (65) = 32
  • 在编程中,如果你想把大写转小写,只需 +32;小写转大写,只需 -32

A, B, C, D, E, F, G,H, I, J, K, L, M, N,O, P, Q, R, S, T,U, V, W, X, Y, Z

(9)打印前多少字符

打印前20个字符

1
printf("%.20s",arr);

(10)printf打印

数据类型 printf (打印) scanf (读取) 为什么?
int (n) 地址 (&n) scanf 必须知道变量在哪,才能把存进去。
float (f) 地址 (&f) 同上。
char (c) 地址 (&c) 同上。
字符串 地址 (s) 地址 (s) 字符串本质就是地址,所以两者都用地址。
数据类型 关键字 printf (输出) scanf (输入) 备注
字符型 char %c %c 读入单个字符(包括空格)
短整型 short %hd %hd h 代表 half (int 的一半)
整型 int %d %d 最常用的整数格式
长整型 long %ld %ld l 代表 long
长长整型 long long %lld %lld 用于极大整数
单精度浮点 float %f %f 默认保留 6 位小数
双精度浮点 double %f%lf %lf 注意:输入必须用 %lf
字符串 char[] %s %s 遇到空格/回车停止
无符号整型 unsigned int %u %u 仅表示正数
十六进制 int %x %x 以 0x 格式读写
符号 拆解 含义 对应数据类型 字节数 (通常)
%u % + u Unsigned (无符号) unsigned int 4 字节
%hu % + h + u Half + Unsigned unsigned short 2 字节
%d % + d Decimal (有符号十进制) int 4 字节
%hd % + h + d Half + Decimal short 2 字节
%lu % + l + u Long + Unsigned unsigned long 4 或 8 字节

深度避坑指南

陷阱一:double 类型的输入输出不对称。 这是新手最常断命的地方。scanf 必须严格区分:float%fdouble 必须用 %lf。如果你给 double 变量用了 %f,读进去的数据会变成一串毫无逻辑的乱码。但在 printf 输出时,由于 C 语言的提升规则,double%f 也是完全可以的。

陷阱二:char 类型的输入会“吃”空格。 执行 scanf(“%c”, &ch) 时,它会老老实实地读取你输入的每一个字符,包括你随手敲的空格或回车。如果你想自动跳过这些没用的空格,只读真正的字符,你应该在格式串里加个空格,写成 scanf(“ %c”, &ch)

陷阱三:scanf 的地址符 &。 对于 int、float、char 等基本类型,输入时必须在变量名前加 &(如 &a)。但如果是字符串数组(如 char str[100]),由于数组名本身在 C 语言里就是地址,所以 scanf(“%s”, str) 绝对不能加 &


3. 进阶技巧:格式化控制

printf 输出时,你可以精准控制显示效果。使用 %.2f 可以强制保留 2 位小数。使用 %5d 可以保证输出至少占 5 个字符宽度,空间不够时在左侧补空格;如果写成 %-5d 则是左对齐。如果你想在数字前面补零(比如显示时间 08:05),可以使用 %02d


4. 总结记忆法

整数系: 核心是 d (decimal)。短的加 h (half),长的加 l (long),更长的加 ll浮点系: 核心是 f (float)。双精度输入必须加 l,表示 long float字符系: 字符是 c (char),字符串是 s (string)。

(11)scanf读取

  1. 读字符串时(%s):是的,遇到空格就停止

当你使用 %s 读取字符串时,scanf 会从第一个非空白字符(空格、回车、制表符)开始读,直到遇到下一个空白字符为止。

  • 输入Hello World
  • 结果:只读入 Hello,而 World 会留在缓冲区里等待下一次读取。
  1. 读数字时(%d, %f):它会跳过前面的空格

读数字时,scanf自动跳过前面的所有空格,直到看到数字为止;一旦数字读完了,遇到空格它就会停止读取,并将该空格留在缓冲区。

  1. 读字符时(%c):它连空格都读

这是最容易出错的地方。%c 不会跳过任何字符。如果缓冲区里刚好有一个空格或上次输入留下的回车,scanf("%c", &ch) 会直接把这个空格读进去。

(12)fgets用法

1
fgets(str, 100, stdin); // 读入一整行,直到遇到回车

(13)字符串处理

1.字符串读取后需要‘\0’封口

1
2
3
4
char temp[100];
for(int i=0;source[i]!=' ';i++){
temp[i]=source[i];
}

读取完之后需要加’\0’封口!

2.fgets会把’\n’读进去,需要手动删去


the c language
https://reader001-guius.github.io/2025/12/21/mistakes in c language/
作者
Yiran Li
发布于
2025年12月21日
许可协议