相关文章:
C语言——基础查漏补缺(一):超长文帮你理清一些概念
C语言——基础查漏补缺(二):《C程序设计试题汇编》应试概念总结
C语言——基础查漏补缺(四):利用《挑战程序设计竞赛(第二版)》前两章学习经典算法
△注:谭浩强红书的第一轮复习略看了前两章(正文概念和课后习题。正文例题没略看),所以第二轮开始前,需要将前两章内容逐行审查,不熟练文字概念补充总结即可。
△注1:谭浩强红书是指谭浩强C语言和配套的学习辅导两本书(后者去年写了未完的blog,内容粘贴至本文,那篇就不要了)。
△注2:谭浩强红书的第一轮搞定后,开始谭浩强绿书(试题汇编),同时看对应章节的校本教材的编程题并总结(校本教材概念部分可先跳过,之后酌情处理,但概念题需要做和总结),这两本书总结至同一篇blog(前年写的C语言查漏补缺二)。
这篇文章是考研复试前的复习笔记,简单修改后分享给需要的人。以上删除线是我碎碎念的计划。
这章主要是概念题,自行总结。
1,☆闰年的定义:
#include
int main(){
float res = 0;
int i, flag = -1;
for(i = 1;i <= 100;++i){
flag = flag * (-1);
res = res + (flag * (1 / i));
}
printf("%f\n", res);
return 0;
}
上述结果输出为1.000000
,并且经检查,for中所有res都等于1.000000
。
原因:res = res + (flag * (1 / i));
中的1
和i
都是int型,所以1/i
也是int,并且i>1时它为0.
更正:将i
定义为float
即可,这样1 / i
和flag * (1 / i)
都会被处理为float
。
3,☆质数筛法(留白)
// 埃氏筛法,输入n,输出不大于n的素数个数
#include
#define maxn 1000005
int IsPrime[maxn];
int n;
int main(){
int count = 0;
scanf("%d", &n);
IsPrime[0] = 0;
IsPrime[1] = 0;
for(int i = 2;i <= n;i++)IsPrime[i] = 1;
for(int i = 2;i * i <= n;i++)
if(IsPrime[i]){
for(int j = 2 * i;j <= n;j += i)
IsPrime[j] = 0;
}
for(int i = 2;i <= n;i++)
if(IsPrime[i])count++;
printf("%d\n", count);
return 0;
}
// 区间筛法,输入a和b,输出[a, b)内素数个数
#include
#define maxa 1000000000005
#define maxb 1000000000005
#define maxl 1000005
int IsPrimeMin[maxl], IsPrime[maxl];
long long max(long long a, long long b){
return (a > b) ? a : b;
}
int main(){
long long a, b;
int count = 0;
scanf("%lld %lld", &a, &b)
for(int i = 0;i < b - a;i++)
IsPrime[i] = 1; // 代表i+a是素数
for(int i = 2;(long long)i * i < b;i++)
IsPrimeMin[i] = 1;
for(int i = 2;(long long)i * i < b;i++)
if(IsPrimeMin[i]){
for(int j = 2 * i;(long long)j * j < b;j += i)
IsPrimeMin[j] = 0;
for(long long j = max((a + i - 1) / i, 2LL) * i;j < b;j += i)
IsPrime[j-a] = 0;
}
for(int i = 0;i < b - a;i++)
if(IsPrime[i])count++;
printf("%d\n", count);
return 0;
}
4,算法5特性
有穷性、确定性(步骤无歧义)、有效性(比如除以0是无效的)、可以没输入、必须有输出;
5,△课后有文字描述题预留总结
1,scanf
对应double
的是%lf
,printf
对应double
的可以是%f
或者%lf
。
△△△非常易错:
利用上述公式进行华氏度到摄氏度的转换,阅读下列错误代码,找出错误:
#include
int main(){
double c, f;
scanf("%lf", &f);
c = 5 / 9 * (f - 32);
printf("%lf\n", c);
return 0;
}
错误:c = 5 / 9 * (f - 32);
,错因:5
和9
都是int,5/9
是0,则参与运算的时候5/9
会先被转化为(double)(0.0)
,0乘任何数都得0.
多嘴一句:f-32
也是double型,因为f是double型。
更正:c = 5.0 / 9 * (f - 32);
,当然为了好看,可以将9也变成9.0.
2,常量、常变量、符号常量
3,合法标识符
4,算术类型、纯量类型、组合类型
对于%g
,系统自动选择按照%f
或者%e
输出,选择长度较短的方式,不输出无意义的0.但注意,上述选择了用%e
输出时,指数部分为e+013
,其中有一个0,这不是无意义的0,而是因为%e
指数部分固定为5列。
10,留意scanf
中长型和短型占位符
11,△△△一个scanf
的样例(自己独立判断a,b,c分别什么值)
1,pow(a, b)
的功能是返回 a b a^b ab的值;
2,char
按int
输出
char c1 = 197;
printf("%d\n", c1);
分析上述输出结果,首先197用二进制表示是11000101
,由于char
是8位,所以c1被赋值为11000101
,但是注意这个是c1的补码。而这个二进制第一位的1会被判定为负号,所以这个二进制会被判定为负数的补码,其原码为10111011
,第一位是符号位,这个数字是-59.所以输出为-59.
3,int
按char
输出
int i = 330;
char c = i;
printf("%d\n", c);
分析输出结果,首先330是101001010
,将它赋值给c,c是char型,只有8位,所以c被赋值为01001010
,注意这个01001010
是c的补码,不过由于首位符号位为0,代表c是正数,所以原码也就是它了。01001010
的原码也是01001010
,这是十进制的74,所以上述程序输出结果为74.
1,条件表达式少见用法:
2,警惕switch语句,可能看上去会,实际敲的时候敲错,所以保险起见随便敲一个看看自己是否能敲对;
3,⚠️判断b*b-4*a*c
是否等于0的方法(不大于1e-6
即可)
#include
int main(){
...
if(fabs(b*b-4*a*c)<=1e-6) // 不大于1e-6即可
...
...
}
4,逻辑与优先级高于逻辑或
a || b && c
等价于a || (b && c)
,但省略括号会有警告:
'&&' within '||' [-Wlogical-op-parentheses]
所以最好乖乖加上括号。
5,除以0的报错
Floating point exception: 8
有一次报了这个错误,最终发现代码中有整数除以0的情况。
据说如果%
操作出现对0取余,也会出现这样的报错。
1,for
和getchar
联动例子
for(i=0;(c=getchar())!='\n',i+=c);
上述语句的作用是将输入的字符的ASCII码相加,直到输入换行符。
for(;(c=getchar())!='\n';)
printf("%c", c);
上述语句的作用是每输入一个字符就立刻输出这个字符,直到输入换行符。
#include
int main(){
int sign = 1;
double a = 0.0;
for(double i=1;1/i>=1e-6;i+=2){
a += (sign * 1 / i);
sign *= -1;
}
printf("%10.8f\n", a * 4);
return 0;
}
结果
3.14159065
1,☆☆☆(背)辗转相除法
输入n和m,输出n和m的最大公约数和最小公倍数:
核心算法在内层的while
#include
int main(){
int m, n, p, r;
int temp;
while(scanf("%d %d", &n, &m)==2){
if(n<=m){
temp = m;
m = n;
n = temp;
}
p = m * n;
r = 1; // 余数变量,初始化随便赋值一个非零数
// r=0时的m就时最大公约数,不过注意循环结束时m的值储存在n
// 原始m*n的值除以最大公约数就是最小公倍数
while(r!=0){
r = n % m;
n = m;
m = r;
}
printf("最大公约数:%d\n", n);
printf("最小公倍数:%d\n", p / n);
}
return 0;
}
输入输出样例:
4 3
最大公约数:1
最小公倍数:12
3 6
最大公约数:3
最小公倍数:6
2,处理手动输入的字符,直到回车结束(回车不处理)
while((c=getchar())!='\n')
3,☆求平方根的迭代公式
x n + 1 = 1 2 ( x n + a x n ) x_{n+1}=\frac{1}{2}(x_n+\frac{a}{x_n}) xn+1=21(xn+xna)
要求前后两次求出的x的差的绝对值 ∣ x n + 1 − x n ∣ |x_{n+1}-x_n| ∣xn+1−xn∣小于 1 0 − 5 10^{-5} 10−5,则此时的a的平方根近似为 x n + 1 x_{n+1} xn+1。
初始化 x 0 x_0 x0为 a 2 \frac{a}{2} 2a即可。
代码略,记住算法就会写了。
tip:math.h
中的fabs(x)
可以求x的绝对值
注意上图求的是 f ( x ) = 0 f(x)=0 f(x)=0的近似解,也就是接近x轴的交点。
将这个算法记住即可,编程不难,注意实型变量的赋值即可(比如:实型=整型/整型,这句就是错的,会导致实型变量损失巨量精度)。
⚠️注意:
1,数组的长度必须是常量而不是变量,但有种情况除外(忘了就看C语言总结一);
2,可以用整型数组存放字符型数据,因为字符型数据是按照整数形式(ASCII)存放的。但这样做十分浪费空间;
3,☆☆☆字符数组有些东西容易忘掉,自行看C语言总结一;
4,strcpy
和strncpy
对比
重点注意注释中对printf结果的描述;
⚠️注:简记就是strcpy
会复制\0
,strncpy
是指定几位就复制几位,不会自动复制\0
。
#include
#include
int main(){
char str1[] = "1234567890";
char str2[] = "china";
int l1 = strlen(str1);
int l2 = strlen(str2);
int l3;
strcpy(str1, str2);
printf("%s\n", str1);
l3 = strlen(str1);
printf("%d %d %d\n", l1, l2, l3); // 输出10,5,5
return 0;
}
#include
#include
int main(){
char str1[] = "1234567890";
char str2[] = "china";
int l1 = strlen(str1);
int l2 = strlen(str2);
int l3;
strncpy(str1, str2, 5);
printf("%s\n", str1);
l3 = strlen(str1);
printf("%d %d %d\n", l1, l2, l3); // 输出10,5,10
return 0;
}
复杂版:
简单版会导致一些数字重复被筛掉,徒增不必要的时间复杂度。复杂版见此文:复杂版质数筛,算法写在了文中代码注释里。
2,输出杨辉三角(以空间换便利)
这道题有两种办法,一种空间复杂度小但麻烦——用一个数组依次从左至右从上至下存储杨辉三角中的元素,n行杨辉三角占用 n ( n + 1 ) / 2 n(n+1)/2 n(n+1)/2个存储空间;一种空间复杂度较大但简单——直接用一个二维数组(矩阵)存储杨辉三角,占用 n 2 n^2 n2的空间,虽然这样会浪费约一半的存储空间,但编程方便。
书上给的代码:
先将对角线和第一列元素初始化为1,然后再为其他元素赋值。
总之记住,要用矩阵存储杨辉三角,用较小的浪费空间的代价换取编程的方便是值得的。
杨辉三角是 ( a + b ) n (a+b)^n (a+b)n展开后各项的系数
3,☆☆魔方阵
输入奇数 n n n,输出 1 ∼ n 2 1\sim n^2 1∼n2组成的魔方阵(魔方阵就是行、列、对角线之和都相等)。
巧记
1,Hanoi汉诺塔递归
#include
// 将n号圆盘从a移动到b
void move(int n, char a, char b){
printf("move %d from %c to %c\n", n, a, b);
}
// 将n个从a借助b移动到c
void Hanoi(int n, char a, char b, char c){
if(n>0){
Hanoi(n-1, a, c, b);
move(n, a, c);
Hanoi(n-1, b, a, c);
}
}
int main(){
int n;
char a = 'a', b = 'b', c = 'c';
while(scanf("%d", &n)==1){
Hanoi(n, a, b, c);
}
return 0;
}
1,加强版质数筛法
(留白)
2,⚠️⚠️⚠️scanf输入多个字符串
(关键知识:以回车、空格或tab作为分隔)
char a[100], b[100];
int i = 0;
scanf("%s%s", a, b);
如果输入
how are
和输入
how are
是一样的,a被赋值为how
,b被赋值为are
(多余的部分是'\0'
)
⚠️看一个错误的想法:
char a[100], b[100];
int i = 0;
scanf("%s\n%s", a, b);
输入
how are\nyou
以为a赋值为how are
,b赋值为you
,实际上a和b的值和上一个例子一样。
☆☆☆原因:
scanf输入多个字符时,是用空格、回车或者tab作为分隔,所以输入how are\nyou
时,第一个空格就会成为分隔(这样回车就是结束符,回车后的you
直接被忽略),尽管scanf中试图指定\n
为分隔,但这么做系统不会鸟你。
补充:读程练习:
char a[100], b[100], c[100], d[100];
scanf("%s%s", a, b);
scanf("%s%s", c, d);
printf("%s\n%s\n%s\n%s\n", a, b, c, d);
输入
how are you you\n
输出
how
are
you
you
且注意,直到输入最后一个\n
后,才会一下输出所有结果,因为scanf视其为结束符。
3,☆☆☆你可能以为简单的题——输入一行字符,输出这行字符中最长的单词(假设连续的字母都是单词)
⚠️注释讲了重要错误
#include
void func(char a[]){
int start, i, maxlen = 0, tempstart,
templen = 0, flag = 0;
i = 0;
while(a[i]!='\0'){
if((a[i]<='z'&&a[i]>='a')
||(a[i]<='Z'&&a[i]>='A')){
if(flag==1){
++templen;
}
else{
++templen;
flag = 1;
tempstart = i;
}
}
else{
if(flag==1){
flag = 0;
if(templen>maxlen){
maxlen = templen;
// templen = 0; // 重要错因:这句错放到这里,导致templen只会在比maxlen大的时候才清零
start = tempstart;
}
templen = 0;
}
}
++i;
}
if(flag==1){
// 如果字符串是字母结尾,不加这一段的话会忽略掉最后一个单词
if(templen>maxlen){
maxlen = templen;
start = tempstart;
}
}
while(maxlen-->0){
printf("%c", a[start++]);
}
printf("\n");
}
int main(){
char a[100], i;
gets(a);
func(a);
return 0;
}
4,☆☆☆你以为很简单系列——整数转化为字符串
要求:输入整数,长度未知,以字符串输出。
记住用递归会非常简单
void func(int a){
if(a/10!=0){
func(a/10);
}
printf("%c", (a % 10) + '0');
}
自行学习
(无聊的概念有点多,特别注意指针和数组、指针和多维数组、int (*)[4]类型指针、指针和字符串、函数的指针、指针数组和多重指针)
1,☆☆☆二维数组名≠指针的指针
用户CodeBlove的解释
2,☆☆指针变量控制数组
下列代码试图实现:输入10个数,并输出这10个数。
判断下列代码能否正确实现这个功能。
3,之后的题暂时跳过,原因是这一章例题部分都是概念题,没有算法题,且很多指针的用法很少见,看得太早怕忘且不实用。
1,编写函数,交换两个字符串,要求形参以指针形式出现
△注意和交换两个数字不太一样
void swap(char * a, char * b){
char p[100];
strcpy(p, a);
strcpy(a, b);
strcpy(b, p);
}
2,写一个函数,操作长度n的整型数组,实现下图功能:
很简单,但要求用指针操作,所以以此题为例子
#include
void reverse(int * a, int start, int end){
// int * temp = NULL; // 不建议这样做
int temp;
int * p = a + start;
int * q = a + end;
while(p<q){
temp = *p;
*p = *q;
*q = temp;
++p;
--q;
}
}
void func(int * a, int n, int m){
reverse(a, 0, n-m-1);
reverse(a, n-m, n-1);
reverse(a, 0, n-1); // 不慎犯错:n-1写成n,造成错误abort trap
}
int main(){
int a[10];
for(int i=0;i<10;++i){
scanf("%d", a+i);
}
func(a, 10, 3);
for(int i=0;i<10;++i){
printf("%d ", *(a+i));
}
printf("\n");
return 0;
}
3,输入n,n个人围一圈,123123123…这样报数,报到3的人退出,求最后退出的人是几号,输出之。
#include
int next(int * a,int n, int i){
i = (i + 1) % n;
while(*(a+i)==-1){
i = (i + 1) % n;
}
return i;
}
int func(int * a, int n){
int i, lasti = -1, count = 0;
for(i=0;i!=lasti;i=next(a, n, i)){
++count;
if(count==3){
*(a + i) = -1;
count = 0;
}
lasti = i;
}
return i + 1; // 注意是i+1,因为从1开始计数
}
int main(){
int n, a[100];
scanf("%d", &n);
printf("%d\n", func(a, n));
return 0;
}
4,☆数组名为实参,指针为形参新用法
注意下面代码的func函数,其中直接对指针a和b进行操作,而不会影响到func外实参的值。
#include
#include
// 将字符数组a从第m位开始复制到b中
void func(char * a, int n, int m, char * b){
for(int i=1;i<=m-1;++i)
++a;
while(*a!='\0'){
*b = *a;
++b;
++a;
}
*b = '\0';
}
int main(){
char a[100], b[100];
int n, m;
scanf("%s", a);
n = strlen(a);
scanf("%d", &m);
func(a, n, m, b);
puts(b);
}
5,☆☆☆用指针控制矩阵转置(学习没用过的指针操作)
注意到它直接用指针指向a[0][0],然后进行操作。可以看出二维数组可以视为按行存储的。
6,△程序纠错题
检查下列代码的错误
(输入5*5矩阵,最大元素和中心交换,并输出。要求用指针控制)
#include
void change(int * p){
// p指向a[0][0]
int temp, max, maxi, maxj, i, j;
int * q = p;
max = *p;
maxi = 0;
maxj = 0;
for(i=0;i<5;++i){
for(j=0;j<5;++j){
if(*(q+5*i+j)>max){
maxi = i;
maxj = j;
max = *(q+5*i+j);
}
}
}
q = p + 5 * i + j;
temp = *q;
*q = *p;
*p = temp;
}
int main(){
int a[5][5], i, j;
int * p = &a[0][0];
for(i=0;i<5;++i){
for(j=0;j<5;++j)
scanf("%d", *(a+i)+j);
}
change(p);
for(i=0;i<5;++i){
for(j=0;j<5;++j){
printf("%d ", *(*(a+i)+j));
}
printf("\n");
}
return 0;
}
答案:
change函数倒数第五行的i和j分别改成maxi和maxj。
⚠️接下来对比指向数组的指针和指针数组
7.1,☆☆☆指向数组的指针
通过读程学会指向一位数组的指针char (*p)[6]
的用法:
(题目是输入十个等长字符串,对其进行冒泡排序)
(复习时这道题需要自己敲一遍)
说明:p
是指向一维字符数组的指针,字符数组的长度为6;str[10][6]
是二维数组,由10个长度为6的一维数组组成;可以将str
赋值给p
,这样p+i
就指向str
二维数组中第i行,*(p+i)
或者p[i]
指向str
中第i行第1个元素,即str[i][0]
。⚠️且注意str
本身也是指向长度为6的一维字符数组的指针,它指向的是str[10][6]
的第1行,所以同理str+i
指向第i行,*(str+i)
或者str[i]
指向str
中第i行第1个元素,即str[i][0]
。
7.2,☆☆☆指针数组
(题目是输入十个不等长字符串,对其进行冒泡排序)
(复习时这道题需要自己敲一遍)
说明:char * p[10]
,p[i]
自己就是一个char *
型指针.
8,课后13-21涉及函数指针等其他少见用法
(留白)
2,判断两个字符串是否相等:
只需看strcmp(a,b)==0
是否成立。
3,同类型的结构体变量可以直接相互赋值
4,结构体数组使用样例,忘了则看:
(上述代码不完整)上述代码作用是将学生按照成绩排序,可见结构体变量之间可以直接相互赋值(图中倒数第二行)
是错的,因为初始化表中只能有一个常量
5,对共用体变量多次赋值,最后一次起作用。
6,同类型共用体变量之间可以相互赋值:
7,作函数参数:
指向共用体变量的指针可以作函数参数;
C99规定共用体变量也可作函数参数。
8,用途:
如果需要对同一段空间安排不同的用途,那么就可以使用共用体。
9,☆应用实例
这是学校的成员表,如果是学生,则第五列存放班级;如果是教师,则第五列存放职位。
假设只有两个人,则定义结构体如下:
第五列用共用体表示即可。
这样,初始化时用如下方法即可:
见C语言查漏补缺一
见C语言查漏补缺一
1,用链表实现约瑟夫环问题
// ch9课后6 输入n,n人围一圈,报数,报到3的退出,
// 输出最后一人的序号,用链表实现
#include
#include // malloc和free
typedef struct node{
int no;
struct node * next;
} * linklist, node;
int num(linklist head){
node * pre, * p, * q;
int count = 0;
if(head->next==head)
return head->no;
count = 2;
p = head->next;
pre = head;
while(p->next!=p){
pre = p;
p = p->next;
++count;
if(count==3){
q = p;
p = p->next;
pre->next = p;
free(q);
count = 1;
}
}
return p->no;
}
// 长度为n的单循环链表
linklist create(int n){
int count = 0;
node * p, * pre, * head;
p = (node*)malloc(sizeof(node));
p->no = count + 1;
++count;
pre = p;
head = p;
while(count<n){
p = (node*)malloc(sizeof(node));
p->no = count + 1;
++count;
pre->next = p;
pre = p;
}
pre->next = head;
return head;
}
int main(){
int n;
scanf("%d", &n);
printf("%d\n", num(create(n)));
return 0;
}