从C语言入门到精通:代码解析与实战
一、第 1 章:将 C 语言用起来相关文件解析
(一)test.cpp
c
运行
int main() {printf("hello world\n");printf("hello bilibili\n");return 0;
}
该文件是 C 语言程序入门级的实现范例,其核心作用在于展示 C 程序最基础的输出功能与程序结构。
从程序的入口来看,主函数main
是整个程序执行的起始点,这是 C 语言规范所明确规定的。在main
函数体内,通过两次调用标准输入输出库中的printf
函数来完成字符串的输出操作。第一次输出"hello world\n"
,第二次输出"hello bilibili\n"
,其中的转义字符\n
承担着换行的功能,它能确保每次输出的字符串在终端上单独占据一行,使输出结果具有良好的可读性。
程序的末尾,主函数返回整数 0。按照 C 语言的约定,主函数返回 0 表示程序已经正常执行并顺利终止,这是一种标准化的程序退出状态标识。
整体而言,此代码虽然逻辑简单,但完整地涵盖了 C 程序的基本构成要素,包括函数的定义形式、库函数的调用方式以及程序返回值的设定规则,为初学者理解 C 程序的运行机制提供了最直观、最基础的实例参考。
(二)1.trash.cpp
c
运行
int main(){
printf("hello world\n");
printf("hello bilibili\n");
int a,b,c,d;
a=b+c+d*2;
printf("a=%d",a);
return 0;
}
该文件在实现基础字符串输出功能的同时,引入了变量的定义与运算操作,但其代码格式与变量处理方式存在一定的不规范性。
程序起始部分,通过printf
函数完成了与 test.cpp 相同的字符串输出任务,即依次打印"hello world\n"
和"hello bilibili\n"
。
随后,定义了四个整型变量a
、b
、c
、d
。需要重点关注的是,这些变量在定义之后均未进行初始化操作。在 C 语言的内存管理机制中,此类未初始化的自动变量(局部变量)所占据的内存空间中存储的是之前遗留的随机数据,因此其初始值是不确定的,通常被称为 “垃圾值”。
接着,通过表达式a = b + c + d * 2
对变量a
进行赋值运算。由于b
、c
、d
的值具有随机性,这直接导致a
的计算结果也呈现出不确定的特征。
最后,使用printf
函数以"a=%d"
的格式将a
的值输出。
该代码不仅展示了变量的定义、赋值与输出的基本流程,更重要的是,它以实际案例揭示了未初始化变量可能引发的程序行为不可预测性问题,从反面强调了在程序设计中对变量进行初始化操作的重要性,为开发者规避此类潜在风险提供了警示。
(三)2.beauti.cpp
c
运行
#include <stdio.h>int main(){printf("hello world\n");printf("hello bilibili\n");int a, b, c, d;a = b + c + d * 2;printf("a = %d", a);return 0;
}
此文件在功能上与 1.trash.cpp 类似,均涉及基础输出与变量操作,但在代码规范性方面有所提升。
首先,通过#include <stdio.h>
预处理指令显式引入了标准输入输出库。这一操作是必要的,因为printf
函数属于标准输入输出库的范畴,只有引入该库才能确保printf
函数的正确声明与调用,避免编译过程中出现函数未声明的错误。
在主函数main
中,起始部分同样使用printf
函数执行字符串输出操作,输出内容为"hello world\n"
和"hello bilibili\n"
,\n
的换行作用保证了输出格式的清晰。
之后,定义了四个整型变量a
、b
、c
、d
,与 1.trash.cpp 相同,这些变量也未进行初始化处理,其值为不确定的垃圾值。
通过表达式a = b + c + d * 2
对a
进行赋值,由于b
、c
、d
值的不确定性,a
的结果同样具有随机性。
最后,函数通过printf("a = %d", a)
将a
的值以格式化方式输出,相比 1.trash.cpp 中的输出格式,增加了空格,使输出结果更易读。
此代码在展示变量定义、运算及输出功能的同时,体现了更规范的代码格式,如变量定义时的空格使用、预处理指令的明确引入等,并且再次强调了变量初始化在程序设计中的重要性,为开发者提供了更具参考价值的规范案例。
二、第 3 章:控制流相关文件解析
(一)28.HZOJ-136.c
c
运行
/*************************************************************************> File Name: 28.HZOJ-136.c> Author: huguang> Mail: hug@haizeix.com> Created Time: ************************************************************************/#include<stdio.h>int main() {int n;scanf("%d", &n);for (int i = 1; i <= n; i++) {if (i % 7 == 0) printf("%d\n", i);}return 0;
}
该文件的核心功能是筛选并输出指定范围内能被 7 整除的整数,重点展现了循环结构与条件判断语句在数据筛选场景中的协同应用。
程序开头通过#include<stdio.h>
引入标准输入输出库,为scanf
和printf
函数的调用提供了必要的支持。
在主函数main
中,首先定义整型变量n
,用于存储用户输入的范围上限值。通过scanf("%d", &n)
函数从标准输入设备(通常为键盘)读取用户输入的整数,并将其存储到变量n
的内存地址中。
随后,使用for
循环结构遍历从 1 到n
的所有整数。for
循环的初始化部分设置为int i = 1
,确定了循环的起始值;循环条件为i <= n
,规定了循环继续执行的判断依据;每次循环结束后执行i++
操作,实现循环变量的递增,确保能够依次访问 1 到n
之间的每个整数。
在循环体内部,通过条件判断语句if (i % 7 == 0)
来筛选符合条件的数。这里的%
是取余运算符,当i
对 7 取余的结果为 0 时,表明i
是 7 的倍数,此时通过printf("%d\n", i)
将该数输出到标准输出设备,并借助\n
实现换行,使输出结果条理清晰。
程序最后返回 0,表示程序已正常执行并退出。
该代码清晰地展示了for
循环的迭代过程和if
条件判断语句在数据筛选中的应用逻辑,充分体现了控制流语句在处理具有规律性数据筛选问题时的高效性和实用性。
(二)29.HZOJ-136.c
c
运行
/*************************************************************************> File Name: 28.HZOJ-136.c> Author: huguang> Mail: hug@haizeix.com> Created Time: ************************************************************************/#include<stdio.h>int main() {int n;scanf("%d", &n);for (int i = 1; i <= n; i++) {if (i % 7) continue;printf("%d\n", i);}return 0;
}
此文件与 28.HZOJ-136.c 的功能完全一致,均用于输出 1 到n
之间能被 7 整除的整数,但在条件判断与循环控制的实现方式上有所不同,引入了continue
语句来优化逻辑。
程序同样先通过#include<stdio.h>
引入标准输入输出库,在主函数中读取用户输入的n
值,for
循环的结构也与 28.HZOJ-136.c 保持一致,用于遍历 1 到n
的整数。
循环体内部的条件判断为if (i % 7) continue
。在 C 语言中,条件判断的结果为非 0 值时表示逻辑真,0 值表示逻辑假。当i % 7
的结果不为 0 时,即i
不能被 7 整除,此时条件为真,程序执行continue
语句。continue
语句的作用是立即跳过当前循环体中剩余的所有语句,直接进入下一次循环的迭代过程。
当i % 7
的结果为 0 时,即i
能被 7 整除,条件判断为假,continue
语句不被执行,程序继续执行循环体中后续的printf("%d\n", i)
语句,将该整数输出。
这种实现方式通过continue
语句提前结束不符合条件的循环迭代,在逻辑上更简洁,避免了不必要的代码块嵌套,体现了循环控制语句在优化程序结构方面的作用。同时,该代码也再次验证了不同控制流语句组合可以实现相同功能,为开发者提供了多样化的编程思路。
三、第 4 章:函数与程序结构相关文件解析
(一)13.HZOJ-464.c
c
运行
/*************************************************************************> File Name: 13.HZOJ-464.c> Author: huguang> Mail: hug@haizeix.com> Created Time: ************************************************************************/#include<stdio.h>int is_leap_year(int x) {if (x % 4 == 0 && x % 100 != 0) return 1;if (x % 400 == 0) return 1;return 0;
}int main() {int x, y, ans = 0;scanf("%d%d", &x, &y);for (int i = x; i <= y; i++) {ans += is_leap_year(i);}printf("%d\n", ans);return 0;
}
该文件的核心功能是计算指定年份区间内闰年的总数量,通过自定义函数封装闰年判断逻辑,充分体现了模块化程序设计的思想。
程序首先通过#include<stdio.h>
引入标准输入输出库,为后续的输入输出操作提供支持。
自定义函数is_leap_year
被设计用于判断单个年份是否为闰年,其参数为整型变量x
(待判断的年份),返回值为整型。该函数内部通过两个条件判断语句实现闰年的判定逻辑:第一个条件x % 4 == 0 && x % 100 != 0
用于判断年份能被 4 整除且不能被 100 整除的情况,满足此条件则返回 1(表示是闰年);第二个条件x % 400 == 0
用于判断年份能被 400 整除的情况,满足此条件也返回 1;若两个条件均不满足,则返回 0(表示不是闰年)。这种逻辑严格遵循了闰年的判定规则,确保了判断结果的准确性。
在主函数main
中,首先定义了三个整型变量x
、y
(分别表示年份区间的起始和结束)和ans
(用于累计闰年的数量,初始化为 0)。通过scanf("%d%d", &x, &y)
函数从标准输入读取用户输入的年份区间。
随后,使用for
循环遍历从x
到y
的每一个年份,在循环体中通过调用is_leap_year(i)
函数判断当前年份i
是否为闰年,并将返回值累加到ans
中。循环结束后,ans
中存储的即为该区间内闰年的总数量。
最后,通过printf("%d\n", ans)
将结果输出,程序返回 0 表示正常退出。
该代码通过将闰年判断逻辑封装到独立的函数中,提高了代码的可读性、可维护性和复用性,是模块化程序设计思想的典型应用案例。
(二)14.HZOJ-465.c
c
运行
/*************************************************************************> File Name: 14.HZOJ-465.c> Author: huguang> Mail: hug@haizeix.com> Created Time: ************************************************************************/#include<stdio.h>int digit(long long n, long long k) {if (k == 1) return n % 10;return digit(n / 10, k - 1);
}int main() {long long n, k;scanf("%lld%lld", &n, &k);printf("%d\n", digit(n, k));return 0;
}
此文件的功能是获取一个整数中指定位置的数字,采用递归函数的方式实现了数字的逐位提取操作,展现了递归算法在解决此类问题时的简洁性。
程序开头引入stdio.h
库,为输入输出函数提供支持。
自定义函数digit
采用递归方式实现,其参数为long long
类型的n
(待提取数字的整数)和k
(指定的位置,从右往左计数,最右边为第 1 位),返回值为整型(提取到的数字)。函数的递归终止条件为k == 1
,此时直接返回n % 10
,即整数n
的最后一位数字;当k
大于 1 时,函数通过递归调用digit(n / 10, k - 1)
来实现,其中n / 10
的操作是去除n
的最后一位数字,k - 1
则是将位置计数减 1,逐步向目标位置靠近,直到满足终止条件。
在主函数main
中,定义了两个long long
类型的变量n
和k
,分别用于存储输入的整数和指定的位置。通过scanf("%lld%lld", &n, &k)
读取用户输入的数据,随后调用digit(n, k)
函数获取指定位置的数字,并通过printf
函数输出结果。
程序的递归实现方式避免了复杂的循环控制,使代码逻辑更加清晰直观,但需要注意的是,递归深度过大会导致栈溢出问题,不过在该问题的应用场景下,由于k
的取值通常不会过大,因此可以安全使用。
(三)15.HZOJ-466.c
c
运行
/*************************************************************************> File Name: 15.HZOJ-466.c> Author: huguang> Mail: hug@haizeix.com> Created Time: ************************************************************************/#include<stdio.h>int is_palindrome(int x) {int z = x, y = 0;while (x) {y = y * 10 + x % 10;x /= 10;}return y == z;
}int main() {int n, ans = 0;scanf("%d", &n);for (int i = 1; i <= n; i++) {ans += is_palindrome(i);}printf("%d\n", ans);return 0;
}
该文件用于计算 1 到指定整数n
之间所有回文数的数量,通过自定义函数is_palindrome
实现回文数的判断,展示了循环结构在数字反转操作中的应用。
程序引入stdio.h
库以支持输入输出功能。
自定义函数is_palindrome
的作用是判断一个整数是否为回文数,参数为整型x
,返回值为整型(1 表示是回文数,0 表示不是)。函数内部首先定义变量z
存储x
的原始值,y
用于存储x
反转后的数字(初始化为 0)。通过while (x)
循环对x
进行反转操作:在每次循环中,y = y * 10 + x % 10
将x
的最后一位数字添加到y
的末尾,x /= 10
则去除x
的最后一位数字,直到x
变为 0(循环结束)。最后,通过return y == z
判断反转后的数字y
是否与原始数字z
相等,若相等则为回文数,返回 1,否则返回 0。
主函数main
中,定义整型变量n
(存储上限值)和ans
(累计回文数数量,初始化为 0)。通过scanf("%d", &n)
读取n
的值,然后使用for
循环遍历 1 到n
的所有整数,在循环中调用is_palindrome(i)
函数进行判断,并将返回值累加到ans
中。循环结束后,通过printf
输出ans
的值,即 1 到n
之间回文数的总数量。
该代码通过清晰的函数分工和高效的数字反转算法,实现了回文数的准确判断与计数,体现了函数封装和循环结构在数据处理中的有效结合。
(四)16.HZOJ-467.c
c
运行
/*************************************************************************> File Name: 16.HZOJ-467.c> Author: huguang> Mail: hug@haizeix.com> Created Time: ************************************************************************/#include<stdio.h>long long f(int n) {if (n == 1) return 1;return n * f(n - 1);
}int main() {int n;scanf("%d", &n);printf("%lld\n", f(n));return 0;
}
此文件用于计算整数n
的阶乘,采用递归函数的方式实现,展示了递归算法在解决阶乘这类具有递推关系问题时的应用。
程序引入stdio.h
库以支持输入输出操作。
自定义函数f
的功能是计算n
的阶乘,参数为整型n
,返回值为long long
类型(由于阶乘结果增长迅速,使用长整型以避免溢出)。函数的递归终止条件为n == 1
,此时返回 1(1 的阶乘为 1);当n
大于 1 时,函数通过return n * f(n - 1)
实现递归调用,即n
的阶乘等于n
乘以n-1
的阶乘,这一逻辑直接体现了阶乘的数学定义。
在主函数main
中,定义整型变量n
(存储待计算阶乘的整数),通过scanf("%d", &n)
读取用户输入的n
值,随后调用f(n)
函数计算阶乘,并通过printf("%lld\n", f(n))
输出结果(注意使用%lld
格式符以匹配long long
类型)。
该代码的递归实现方式简洁明了,直接映射了阶乘的数学定义,但需要注意的是,由于递归调用会占用栈空间,且阶乘结果增长极快,n
的取值不宜过大,否则可能导致栈溢出或结果溢出。
(五)17.HZOJ-468.c
c
运行
/*************************************************************************> File Name: 17.HZOJ-468.c> Author: huguang> Mail: hug@haizeix.com> Created Time: ************************************************************************/#include<stdio.h>int gcd(int a, int b) {if (b == 0) return a;return gcd(b, a % b);
}int main() {int a, b;scanf("%d%d", &a, &b);printf("%d\n", gcd(a, b));return 0;
}
该文件用于计算两个整数的最大公约数(GCD),采用欧几里得算法(辗转相除法)的递归实现方式,展现了递归在数论算法中的高效应用。
程序引入stdio.h
库以支持输入输出功能。
自定义函数gcd
用于计算两个整数a
和b
的最大公约数,参数为两个整型变量a
和b
,返回值为整型(最大公约数)。函数的递归终止条件为b == 0
,此时返回a
(因为当除数为 0 时,被除数即为最大公约数);当b
不为 0 时,函数通过return gcd(b, a % b)
进行递归调用,其中a % b
是a
除以b
的余数,这一操作依据了欧几里得算法的核心思想:两个整数的最大公约数等于其中较小的数和两数相除余数的最大公约数。
在主函数main
中,定义两个整型变量a
和b
,通过scanf("%d%d", &a, &b)
读取用户输入的两个整数,调用gcd(a, b)
函数计算它们的最大公约数,并通过printf
函数输出结果。
欧几里得算法是一种高效的求最大公约数的算法,其递归实现方式使代码极为简洁,充分体现了算法思想与程序实现的紧密结合。
(六)18.HZOJ-185.c
c
运行
/*************************************************************************> File Name: 18.HZOJ-185.c> Author: huguang> Mail: hug@haizeix.com> Created Time: ************************************************************************/#include<stdio.h>int f(int n) {if (n <= 2) return 1;return f(n - 1) + f(n - 2);
}int main() {int n;scanf("%d", &n);printf("%d\n", f(n));return 0;
}
此文件用于计算斐波那契数列的第n
项,采用递归函数的方式实现,展示了递归在求解递推关系数列中的应用,但也存在一定的性能局限。
程序引入stdio.h
库以支持输入输出操作。
自定义函数f
用于计算斐波那契数列的第n
项,参数为整型n
,返回值为整型。斐波那契数列的定义为:第 1 项和第 2 项均为 1,从第 3 项开始,每一项都等于前两项之和。因此,函数的递归终止条件为n <= 2
,此时返回 1;当n
大于 2 时,函数通过return f(n - 1) + f(n - 2)
实现递归调用,即第n
项等于第n-1
项与第n-2
项之和。
在主函数main
中,定义整型变量n
(表示要计算的项数),通过scanf("%d", &n)
读取用户输入的n
值,调用f(n)
函数计算斐波那契数列的第n
项,并通过printf
函数输出结果。
需要注意的是,该递归实现方式存在严重的性能问题,因为在计算过程中会大量重复计算相同的项(如计算f(5)
时需要计算f(4)
和f(3)
,计算f(4)
时又需要计算f(3)
和f(2)
,其中f(3)
被重复计算),导致时间复杂度呈指数级增长。因此,这种实现方式仅适用于n
值较小的情况,对于较大的n
,通常采用迭代方式或记忆化递归进行优化。
(七)19.HZOJ-183.c
c
运行
/*************************************************************************> File Name: 19.HZOJ-183.c> Author: huguang> Mail: hug@haizeix.com> Created Time: ************************************************************************/#include<stdio.h>int f(int x) {if (x <= 0) return 0;if (x == 1) return 1;if (x % 2 == 0) return 3 * f(x / 2) - 1;return 3 * f((x + 1) / 2) - 1;
}int main() {int x;scanf("%d", &x);printf("%d\n", f(x));return 0;
}
该文件实现了一个基于特定递归公式的函数计算,展示了递归在处理分治类问题中的应用,通过条件判断实现了不同情况下的递归分支处理。
程序引入stdio.h
库以支持输入输出功能。
自定义函数f
的参数为整型x
,返回值为整型,其计算逻辑基于以下规则:当x <= 0
时,返回 0;当x == 1
时,返回 1;当x
为偶数时,返回3 * f(x / 2) - 1
;当x
为奇数时,返回3 * f((x + 1) / 2) - 1
。这种分段的递归逻辑使得函数能够根据x
的不同取值进行相应的计算,体现了分治思想,即将较大的问题分解为较小的子问题进行求解。
在主函数main
中,定义整型变量x
,通过scanf("%d", &x)
读取用户输入的x
值,调用f(x)
函数进行计算,并通过printf
函数输出结果。
该代码的递归实现方式清晰地体现了问题的分治策略,将复杂的计算过程分解为多个可重复的子问题,使代码逻辑具有良好的结构化特征。
(八)20.HZOJ-235.cpp
c
运行
/*************************************************************************> File Name: 3.HZOJ-235.cpp> Author: huguang> Mail: hug@haizeix.com> Created Time: ************************************************************************/#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <queue>
#include <stack>
#include <algorithm>
#include <string>
#include <map>
#include <set>
#include <vector>
using namespace std;int arr[10];void print_one_result(int n) {for (int i = 0; i <= n; i++) {if (i) cout << " ";cout << arr[i];}cout << endl;return ;
}void f(int i, int j, int n) {if (j > n) return ;for (int k = j; k <= n; k++) {arr[i] = k;print_one_result(i);f(i + 1, k + 1, n);}return ;
}int main() {int n;cin >> n;f(0, 1, n);return 0;
}
此文件是一个 C++ 程序,用于输出从 1 到n
的所有可能的递增子序列(包括长度为 1 的子序列),采用递归与回溯的思想实现,展示了递归在组合问题中的应用。
程序开头引入了多个 C++ 标准库,包括输入输出流库、标准库、容器库等,并通过using namespace std
简化了标准库中标识符的使用。
全局数组arr
用于存储当前生成的子序列元素。函数print_one_result
的功能是打印当前生成的子序列,参数n
表示子序列的长度(索引从 0 到n
),通过for
循环遍历arr
数组的前n+1
个元素并输出,元素之间用空格分隔。
核心递归函数f
的参数包括i
(当前子序列的长度索引)、j
(当前可选数字的起始值)和n
(上限值)。函数首先判断若j > n
,则表示没有可选数字,直接返回;否则,通过for
循环从j
到n
选取数字k
,将其存入arr[i]
中,调用print_one_result(i)
打印当前子序列(长度为i+1
),然后递归调用f(i + 1, k + 1, n)
生成更长的子序列(下一个元素从k+1
开始选取,以保证子序列的递增性)。
主函数main
中,通过cin
读取n
的值,调用f(0, 1, n)
启动递归过程,生成并打印所有符合条件的子序列。
该程序通过递归与循环的结合,系统地生成了所有递增子序列,体现了组合问题求解中常见的回溯思想,即通过不断尝试和递归深入来探索所有可能的解。
(九)21.HZOJ-236.cpp
c
运行
/*************************************************************************> File Name: 4.HZOJ-236.cpp> Author: huguang> Mail: hug@haizeix.com> Created Time: ************************************************************************/#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <queue>
#include <stack>
#include <algorithm>
#include <string>
#include <map>
#include <set>
#include <vector>
using namespace std;int arr[10];void print_one_result(int n) {for (int i = 0; i < n; i++) {if (i) cout << " ";cout << arr[i];}cout << endl;return ;
}void f(int i, int j, int n, int m) {if (i == m) {print_one_result(m);return ;}for (int k = j; k <= n && m - i - 1 <= n - k; k++) {arr[i] = k;f(i + 1, k + 1, n, m);}return ;
}int main() {int n, m;cin >> n >> m;f(0, 1, n, m);return 0;
}
该文件是一个 C++ 程序,用于输出从 1 到n
中选取m
个元素的所有组合(不考虑顺序,且元素递增),采用递归方式实现,展示了组合数生成的典型算法。
程序引入了多个 C++ 标准库,并使用using namespace std
简化标识符使用。全局数组arr
用于存储当前组合的元素。
函数print_one_result
用于打印一个组合,参数n
为组合的长度m
,通过for
循环遍历arr
数组的前m
个元素并输出,元素间用空格分隔。
核心递归函数f
的参数包括i
(当前组合中已选取的元素个数索引)、j
(当前可选数字的起始值)、n
(数字范围上限)和m
(需要选取的元素个数)。函数的终止条件为i == m
,此时表示已选取m
个元素,调用print_one_result(m)
打印该组合并返回。
在循环部分,for
循环的条件k <= n && m - i - 1 <= n - k
用于优化选取过程,其中m - i - 1 <= n - k
确保剩余可选数字的数量足够选取剩余需要的元素(m - i - 1
个)。在循环中,将当前数字k
存入arr[i]
,然后递归调用f(i + 1, k + 1, n, m)
,继续选取下一个元素(从k + 1
开始,保证组合元素的递增性)。
主函数main
中,通过cin
读取n
和m
的值,调用f(0, 1, n, m)
启动递归,生成并打印所有符合条件的组合。
该程序通过合理的递归设计和循环条件优化,高效地生成了所有指定长度的组合,体现了组合问题求解的核心思想。
(十)22.HZOJ-237.cpp
c
运行
/*************************************************************************> File Name: 5.HZOJ-237.cpp> Author: huguang> Mail: hug@haizeix.com> Created Time: ************************************************************************/#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <queue>
#include <stack>
#include <algorithm>
#include <string>
#include <map>
#include <set>
#include <vector>
using namespace std;int arr[10], vis[10] = {0};void print_one_result(int n) {for (int i = 0; i < n; i++) {if (i) cout << " ";cout << arr[i];}cout << endl;return ;
}void f(int i, int n) {if (i == n) {print_one_result(n);return ;}for (int k = 1; k <= n; k++) {if (vis[k]) continue;arr[i] = k;vis[k] = 1;f(i + 1, n);vis[k] = 0;}return ;
}int main() {int n;cin >> n;f(0, n);return 0;
}
此文件是一个 C++ 程序,用于输出 1 到n
的所有全排列,采用递归与回溯的方法实现,展示了排列问题的典型求解思路。
程序引入了必要的 C++ 标准库,并使用using namespace std
简化操作。全局数组arr
用于存储当前生成的排列,vis
数组(初始化为 0)用于标记数字是否已被使用(1 表示已使用,0 表示未使用)。
函数print_one_result
用于打印一个完整的排列,参数n
为排列的长度,通过for
循环输出arr
数组中的n
个元素,元素间用空格分隔。
核心递归函数f
的参数为i
(当前排列中已确定的元素个数索引)和n
(排列的长度)。当i == n
时,表示已生成一个完整的排列,调用print_one_result(n)
打印该排列并返回。
在循环部分,for
循环从 1 到n
遍历每个数字k
,通过if (vis[k]) continue
跳过已使用的数字。对于未使用的数字,将其存入arr[i]
,并将vis[k]
标记为 1(表示已使用),然后递归调用f(i + 1, n)
继续生成排列的下一位。递归返回后,将vis[k]
重新标记为 0(回溯操作),以便该数字在其他排列中可以被再次使用。
主函数main
中,通过cin
读取n
的值,调用f(0, n)
启动递归过程,生成并打印所有全排列。
该程序通过递归探索所有可能的数字排列,并利用回溯操作确保每个数字在排列中只出现一次,完整地实现了全排列的生成,是回溯算法的典型应用案例。
四、第 5 章:指针与数组相关文件解析
(一)10.HZOJ-144.c
c
运行
/*************************************************************************> File Name: 10.HZOJ-144.c> Author: huguang> Mail: hug@haizeix.com> Created Time: ************************************************************************/#include <stdio.h>
#include <string.h>int main() {char str[105];scanf("%s", str);int cnt = 0;for (int i = 0, n = strlen(str); i < n; i++) {if (str[i] == 'A') cnt += 1;}printf("%d\n", cnt);return 0;
}
该文件用于统计一个字符串中字符 'A' 出现的次数,展示了字符数组的使用以及字符串长度函数strlen
的应用。
程序引入了stdio.h
(支持输入输出)和string.h
(支持字符串操作函数)。
在主函数main
中,定义了一个大小为 105 的字符数组str
,用于存储输入的字符串。通过scanf("%s", str)
从标准输入读取字符串(%s
格式符会自动忽略空格和换行,直到遇到下一个空白字符为止)。
定义整型变量cnt
(初始化为 0)用于累计 'A' 的出现次数。for
循环的初始化部分通过strlen(str)
获取字符串str
的长度(不包括终止符 '\0')并赋值给n
,循环条件为i < n
,确保遍历字符串中的每个字符。在循环体中,通过if (str[i] == 'A') cnt += 1
判断当前字符是否为 'A',若是则将cnt
加 1。
循环结束后,通过printf("%d\n", cnt)
输出统计结果,程序返回 0。
该代码利用字符数组存储字符串,通过strlen
函数获取字符串长度,再结合循环遍历实现字符统计,清晰地展示了字符串处理的基本方法。
(二)11.HZOJ-144.c
c
运行
/*************************************************************************> File Name: 10.HZOJ-144.c> Author: huguang> Mail: hug@haizeix.com> Created Time: ************************************************************************/#include <stdio.h>
#include <string.h>int main() {char str[105];scanf("%s", str);int cnt = 0;for (int i = 0; str[i]; i++) {cnt += (str[i] == 'A');}printf("%d\n", cnt);return 0;
}
此文件与 10.HZOJ-144.c 功能相同,均用于统计字符串中 'A' 的出现次数,但在循环条件和计数方式上有所简化,展示了字符串终止符的特性及条件表达式的应用。
程序同样引入stdio.h
和string.h
库,定义字符数组str
并读取输入字符串。
在计数逻辑上,for
循环的条件为str[i]
,利用了 C 语言中字符串以 '\0'(空字符,ASCII 值为 0)结尾的特性,当i
遍历到字符串末尾时,str[i]
的值为 '\0',条件为假,循环终止,无需显式调用strlen
函数获取长度,简化了代码。
在计数方式上,cnt += (str[i] == 'A')
利用了条件表达式的结果(真为 1,假为 0),当str[i]
是 'A' 时,表达式str[i] == 'A'
的值为 1,cnt
加 1;否则为 0,cnt
不变,相比if
语句更加简洁。
最后,输出统计结果cnt
。
该代码通过利用字符串的终止符特性和条件表达式的数值特性,使代码更加简洁高效,展示了 C 语言中字符串处理的灵活方式。
(三)12.HZOJ-145.c
c
运行
/*************************************************************************> File Name: 12.HZOJ-145.c> Author: huguang> Mail: hug@haizeix.com> Created Time: ************************************************************************/#include <stdio.h>
#include <string.h>int main() {char s[105], t[105];int s_len, t_len = -1;int n;scanf("%d", &n);for (int i = 0; i < n; i++) {scanf("%s", s);s_len = strlen(s);if (s_len > t_len) {strcpy(t, s);t_len = s_len;}}printf("%s\n", t);return 0;
}
该文件用于从n
个输入的字符串中找出最长的字符串并输出,展示了字符数组的赋值(通过strcpy
函数)和字符串长度比较的操作。
程序引入stdio.h
和string.h
库,定义两个字符数组s
(用于存储当前输入的字符串)和t
(用于存储最长的字符串),以及整型变量s_len
(当前字符串长度)、t_len
(最长字符串长度,初始化为 - 1,确保第一个字符串能被正确存入)和n
(字符串的个数)。
通过scanf("%d", &n)
读取n
的值,然后使用for
循环读取n
个字符串。在每次循环中,读取字符串到s
中,通过strlen(s)
获取其长度并赋值给s_len
。若s_len
大于当前的t_len
,则通过strcpy(t, s)
将s
中的字符串复制到t
中(strcpy
函数会将源字符串连同终止符一起复制),并更新t_len
为s_len
。
循环结束后,t
中存储的即为最长的字符串,通过printf("%s\n", t)
输出。
该代码通过循环遍历所有字符串,结合字符串长度比较和复制操作,实现了最长字符串的查找,展示了字符串处理函数在实际问题中的应用。
(四)13.HZOJ-146.c
c
运行
/*************************************************************************> File Name: 13.HZOJ-146.c> Author: huguang> Mail: hug@haizeix.com> Created Time: ************************************************************************/#include <stdio.h>
#include <ctype.h>int main() {char s[55];scanf("%s", s);for (int i = 0; s[i]; i++) {if (islower(s[i])) {if (s[i] == 'z') s[i] = 'a';else s[i] += 1;}if (isupper(s[i])) {if (s[i] == 'Z') s[i] = 'A';else s[i] += 1;}}printf("%s\n", s);return 0;
}
此文件用于将输入字符串中的每个字母向后移动一位('z' 移到 'a','Z' 移到 'A'),展示了字符分类函数islower
和isupper
的使用以及字符的算术运算。
程序引入stdio.h
(输入输出)和ctype.h
(字符处理函数)。
在主函数main
中,定义字符数组s
存储输入的字符串,通过scanf("%s", s)
读取字符串。
for
循环通过str[i]
作为条件遍历字符串中的每个字符。对于每个字符,首先通过islower(s[i])
判断是否为小写字母:若是,且该字符是 'z',则将其改为 'a';否则,将其 ASCII 值加 1(实现向后移动一位)。接着,通过isupper(s[i])
判断是否为大写字母:若是,且该字符是 'Z',则将其改为 'A';否则,同样将 ASCII 值加 1。
循环结束后,字符串s
中的所有字母均已完成移位操作,通过printf("%s\n", s)
输出结果。
该代码利用字符的 ASCII 值特性进行算术运算,结合字符分类函数实现了字母的循环移位,展示了字符处理的灵活性。
(五)15.HZOJ-148.c
c
运行
/*************************************************************************> File Name: 15.HZOJ-148.c> Author: huguang> Mail: hug@haizeix.com> Created Time: ************************************************************************/#include <stdio.h>
#include <string.h>int main() {char s[55];scanf("%s", s);for (int i = 0, j = strlen(s) - 1; i < j; i++, j--) {s[i] ^= s[j]; // c = a ^ bs[j] ^= s[i]; // a = c ^ bs[i] ^= s[j]; // b = c ^ a}printf("%s\n", s);return 0;
}
该文件用于将输入的字符串进行反转,采用异或运算实现两个字符的交换,展示了字符串反转的算法及位运算的应用。
程序引入stdio.h
和string.h
库,定义字符数组s
存储输入的字符串,通过scanf("%s", s)
读取字符串。
for
循环的初始化部分设置i = 0
(字符串起始索引)和j = strlen(s) - 1
(字符串末尾索引,strlen(s)
获取字符串长度),循环条件为i < j
(确保只交换前半部分与后半部分的字符),每次循环后i
递增、j
递减。
在循环体中,通过三次异或运算实现s[i]
和s[j]
的交换,无需额外的临时变量:
s[i] ^= s[j]
:将s[i]
与s[j]
的异或结果存储在s[i]
中(此时s[i]
相当于a ^ b
,s[j]
仍为b
)。s[j] ^= s[i]
:s[j]
与当前s[i]
(a ^ b
)异或,结果为b ^ (a ^ b) = a
,存储在s[j]
中(此时s[j]
为a
)。s[i] ^= s[j]
:s[i]
(a ^ b
)与当前s[j]
(a
)异或,结果为(a ^ b) ^ a = b
,存储在s[i]
中,完成交换。
循环结束后,字符串s
已被反转,通过printf("%s\n", s)
输出结果。
该代码通过高效的异或交换算法和双指针遍历方式,实现了字符串的反转,展示了位运算在内存优化方面的优势。
(六)16.HZOJ-149.c
c
运行
/*************************************************************************> File Name: 16.HZOJ-149.c> Author: huguang> Mail: hug@haizeix.com> Created Time: ************************************************************************/#include <stdio.h>
#include <string.h>int main() {char s[55];while (scanf("%s", s) != EOF) ;printf("%lu\n", strlen(s));return 0;
}
此文件用于读取多个字符串,并输出最后一个字符串的长度,展示了scanf
函数在循环中读取输入直至文件结束(EOF)的用法。
程序引入stdio.h
和string.h
库,定义字符数组s
用于存储输入的字符串。
while (scanf("%s", s) != EOF)
循环不断通过scanf("%s", s)
读取字符串,每次读取会覆盖之前存储在s
中的内容。scanf
函数在成功读取时返回所读取的项目数,当遇到文件结束(如键盘输入时按下 Ctrl+D)时返回 EOF(通常定义为 - 1),此时循环终止。
循环结束后,s
中存储的是最后一个读取到的字符串,通过strlen(s)
获取其长度,并以%lu
格式(size_t
类型对应的格式符)输出。
该代码通过循环读取输入直至 EOF,实现了对最后一个字符串长度的获取,展示了输入流处理的基本方法。
五、第 6 章:预处理命令与结构体相关文件解析
(一)6.project4/part1.c
c
运行
int main() {int a = 123, b = 456;#include "part2.c"
该文件是一个不完整的 C 程序片段,展示了预处理指令#include
在函数体内的使用方式,体现了预处理阶段文件包含的特性。
程序定义了主函数main
,并在函数体内定义了整型变量a
和b
,随后使用#include "part2.c"
预处理指令。在预处理阶段,编译器会将part2.c
文件的内容插入到该指令所在的位置,从而将两个文件的代码合并为一个完整的程序。
这种在函数体内包含文件的方式虽然语法上允许,但不符合常规的编程规范,可能导致代码结构混乱,降低可读性和可维护性,通常不推荐使用。
(二)6.project4/test.c
c
运行
/*************************************************************************> File Name: test.c> Author: huguang> Mail: hug@haizeix.com> Created Time: ************************************************************************/#include <stdio.h>
#include <def_a_b.txt>int main() {printf("a + b = %d\n", a + b);return 0;
}
该文件展示了#include
指令包含非标准头文件(此处为def_a_b.txt
)的用法,用于引入外部定义的变量。
程序引入stdio.h
库以支持输出功能,通过#include <def_a_b.txt>
指令包含def_a_b.txt
文件。假设def_a_b.txt
文件中定义了变量a
和b
(如int a = 10; int b = 20;
),则在预处理阶段,该文件的内容会被插入到当前位置,使得main
函数中可以直接使用变量a
和b
。
主函数main
中,通过printf
函数输出a + b
的结果,展示了包含外部文件定义的变量的使用方式。
这种用法在一定程度上实现了代码的分离,但使用文本文件作为包含文件并非标准做法,通常建议使用.h
头文件来声明变量或函数。
(三)output.c
c
运行
int main() {printf("hello world\n");return 0;
}
该文件是一个简单的 C 程序,功能与第 1 章中的 test.cpp 类似,用于输出"hello world\n"
字符串。
程序定义了主函数main
,通过printf
函数输出字符串,其中\n
实现换行,最后返回 0 表示程序正常退出。
该代码是 C 语言中最基础的输出示例,展示了程序的最简结构。
(四)output2.c
c
运行
int main() {printf("PI = %lf\n", 3.1415926);printf("S(3, 4) = %d\n", 3 * 4);printf("S(3 + 7, 4) = %d\n", 3 + 7 * 4);int * p;int n = 123;p = &n;{ printf("define P : %d\n", *p); };return 0;
}
此文件用于展示不同类型数据的输出格式以及指针的基本使用,包含了多种输出场景和简单的指针操作。
程序定义了主函数main
,通过多个printf
函数输出不同的内容:
printf("PI = %lf\n", 3.1415926)
:以%lf
格式输出双精度浮点数 3.1415926。printf("S(3, 4) = %d\n", 3 * 4)
:输出 3 乘以 4 的结果,以%d
格式(整型)展示。printf("S(3 + 7, 4) = %d\n", 3 + 7 * 4)
:根据运算符优先级,先计算 7 乘以 4,再加上 3,输出结果。
随后,定义了整型指针p
和整型变量n
(初始化为 123),通过p = &n
将p
指向n
的内存地址。在代码块{ printf("define P : %d\n", *p); }
中,通过*p
访问指针所指向的变量n
的值并输出。
该代码展示了不同数据类型的输出格式控制以及指针的基本操作(指针定义、赋值、解引用),为理解格式化输出和指针概念提供了实例。
六、第 7 章:重学输入输出相关文件解析
(一)6.terminal_output.c
c
运行
int main() {freopen("/dev/ttys001", "w", stdout);char s[1000];while (scanf("%s", s) != EOF) {printf("from tty000 : %s\n", s);}return 0;
}
该文件展示了标准输出重定向的操作,通过freopen
函数将标准输出流(stdout)重定向到指定的终端设备。
程序定义了主函数main
,首先调用freopen("/dev/ttys001", "w", stdout)
,其中:
"/dev/ttys001"
是目标终端设备的路径。"w"
表示以写入模式打开该设备。stdout
是标准输出流,该函数调用后,所有通过printf
等函数输出的内容将被发送到/dev/ttys001
设备,而非默认的终端。
随后,定义字符数组s
用于存储输入的字符串,通过while (scanf("%s", s) != EOF)
循环不断读取输入的字符串,每次读取后,通过printf
函数输出带有前缀"from tty000 : "
的字符串。
该代码展示了文件流重定向的基本方法,常用于将输出发送到不同的设备或文件,在调试和多终端交互场景中有一定的应用。
七、第 8 章:文件操作相关文件解析
(一)13.project/main.c
c
运行
/*************************************************************************> File Name: main.c> Author: hug> Mail: hug@haizeix.com> Created Time: 六 1/27 16:38:22 2024************************************************************************/#include <stdio.h>
#include <database.h>int main() {run_database();return 0;
}
该文件是一个数据库程序的主入口文件,展示了如何引入自定义头文件并调用其中声明的函数。
程序引入stdio.h
标准库和自定义头文件database.h(假设该头文件中声明了run_database
函数)。
主函数main
中,通过调用run_database()
函数启动数据库程序,该函数的具体实现可能在与database.h对应的源文件中。
该代码体现了模块化程序设计中主程序与功能模块的分离,主函数负责启动程序,具体功能由其他模块实现。
(二)4.student_system.c
c
运行
void output_to_file(Student *arr, int n) {FILE *fp = fopen(file_name, "a");for (int i = 0; i < n; i++) {fprintf(fp, "%s %d %d %.2lf\n",arr[i].name, arr[i].age,arr[i].class, arr[i].height);}fclose(fp);return ;
}
该文件定义了一个用于将学生信息写入文件的函数output_to_file
,展示了结构体数组的访问和文件写入操作。
函数output_to_file
的参数为Student
类型的指针arr
(指向学生结构体数组)和整型n
(学生数量)。
函数内部首先通过fopen(file_name, "a")
以追加模式("a")打开名为file_name
的文件,返回文件指针fp
(file_name
和Student
结构体的定义可能在其他文件中)。
通过for
循环遍历学生数组,对于每个学生,使用fprintf(fp, "%s %d %d %.2lf\n", ...)
函数将其信息(姓名、年龄、班级、身高)按照指定格式写入文件,其中%.2lf
表示将身高(双精度浮点数)保留两位小数输出。
循环结束后,通过fclose(fp)
关闭文件指针,释放资源。
该代码展示了结构体数组的遍历访问、文件的追加打开方式以及格式化写入文件的操作,是文件操作与结构体结合的典型应用。
(三)9.student_system.c
c
运行
long output_to_file(Student *arr, int n) {FILE *fp = fopen(file_name, "a");fseek(fp, 0, SEEK_END);long offset = ftell(fp);for (int i = 0; i < n; i++) {fprintf(fp, output_format "\n",arr[i].name, arr[i].age,arr[i].class, arr[i].height);}fclose(fp);return offset;
}
此文件与 4.student_system.c 功能类似,均用于将学生信息写入文件,但增加了获取文件当前偏移量的功能,展示了fseek
和ftell
函数的使用。
函数output_to_file
的返回值为long
类型,表示写入操作开始时的文件偏移量。
函数内部,在打开文件后,通过fseek(fp, 0, SEEK_END)
将文件指针移动到文件末尾(SEEK_END
表示相对于文件末尾,偏移量 0),然后通过ftell(fp)
获取当前文件指针的位置(即文件的当前长度),并将其存储到offset
中,作为函数的返回值。
写入学生信息时,使用了宏output_format
(假设其定义了输出格式字符串),使格式字符串的定义与使用分离,提高了代码的可维护性。
最后,关闭文件并返回offset
。
该代码通过fseek
和ftell
函数获取文件偏移量,可用于记录数据在文件中的存储位置,方便后续对数据进行查找或修改。
(四)11.student_system.c
c
运行
long output_to_file(Student *arr, int n) {FILE *fp = fopen(file_name, "ab");fseek(fp, 0, SEEK_END);long offset = ftell(fp);fwrite(arr, sizeof(Student), n, fp);fclose(fp);return offset;
}
该文件同样用于将学生信息写入文件,但采用二进制模式写入,展示了fwrite
函数在二进制文件操作中的应用。
函数output_to_file
以二进制追加模式("ab")打开文件,与文本模式相比,二进制模式不会对数据进行转换,更适合存储结构体等二进制数据。
在获取文件偏移量offset
后,通过fwrite(arr, sizeof(Student), n, fp)
函数将学生结构体数组写入文件,其中:
arr
是要写入的数据的起始地址。sizeof(Student)
是每个数据元素的大小(字节数)。n
是要写入的元素个数。fp
是文件指针。
fwrite
函数直接将结构体的二进制数据写入文件,相比fprintf
的文本写入方式,更加高效,且能精确保存数据的二进制表示。
最后,关闭文件并返回offset
。
该代码展示了二进制文件的打开方式和fwrite
函数的使用,适用于需要高效存储和精确还原数据的场景。