前言:
0.这篇文章适合谁?
适合像我一样的初学者,有一定的C语言基础,但基本功不扎实。
1.为什么要写这篇文章?
在学习C语言,刷OJ题时,我愈发感觉到整理的重要性。又因期末临考,整理学习札记的方法亦是一种好的复习手段。
2.学习札记的定位是什么?
我将以目前自身的能力和对C语言的一些肤浅的看法来反映初学者眼中的疑惑点和重难点。
3.如果有好的想法或问题,如何联系我?
作为初学者,对C语言的理解毕竟肤浅,如有问题,不吝赐教。
方法1:邮箱:wei.haoran@outlook;邮箱:hrwei@outlook;
方法2:通过CSDN私信或者对文章评论;
4.参考书目:
《软件开发的201个原则》【美】Alan M. Davis 电子工业出版社
《人月神话》【美】Frederick P. Brooks, Jr. 清华大学出版社
《C Primer Plus》(第6版)【美】Stephen Prata 人民邮电出版社
《C语言解惑》【美】Alan R. Feuer 人民邮电出版社
《C陷阱与缺陷》【美】Andrew Koenig 人民邮电出版社
《明解C语言》(第3版)【日】柴田望洋 人民邮电出版社
《The Development of the C Language》 Dennis M. Ritchie,Bell-Labs
目录
正文:
1.运算符的优先级辨析;
2.赋值操作符提示;
3.逻辑操作符辨析;
4.增量操作符明确;
题外话:词法分析器的“贪心法”;
5.关系操作符认知;
6.Printf和Scanf;
7.getchar()和putchar();
8.gets和puts;
9.常量与变量;
10.基本数据类型;
11.C语言判断语句;
12.循环和递归;
13.跳转语句;
14.标识符问题;
15.逗号表达式;
16.薛定谔的函数;
17.奇妙的作用域规则;
18.谜一般的数组;
19,魔幻的结构体;
20,排序算法;
如何阅读操作符优先级表格?
1.由上到下,操作符的优先级依次降低;
2.如果在表达式内遇到相同级别操作符,则依据其关联原则;
Attention!
注意一元‘+’和一元‘-’一元操作符辨析;
<x = -3 + 4 * 5 - 6;
我们根据潜意识,就可以得知 x = 11 ;
但是,我要解释一下计算机对这段表达式的操作:
1. 加减操作符‘-’ ‘+’优先级低于乘法操作符 ‘*’ ; 一元操作符‘-’优先级最高;
x = (-3)+(4*5)- 6;
2.这时,括号外的加减操作符‘+’和‘-’优先级一致,关联规则由左到右;因此,
x = ((-3) + (4*5)) - 6; x = (((-3) + (4*5)) - 6);最终 x = 11;
OK,我们再看一道题:
x = -3 * 4 % - 6 / 5;
是不是有些懵圈?不要慌,按照上面的步骤来计算;
-3 和 -6 都是一元操作符,这个‘-’表明‘负’;x = (-3)* 4 % (-6)/ 5;
一元操作符以后,就是乘除操作符(关联规则从左至右);
x = ((-3)* 4 ) % (-6)/ 5;
x = (((-3)* 4 ) % (-6))/ 5;
x = ((((-3)* 4 ) % (-6))/ 5);
最终,x = 0 ;
方法同理,只需要记住’==‘ 和 ‘!=’ 是比较运算符,优先级高于赋值运算符;关联规则:从左至右
出个题:
int x,y,z;
x = 2;
x *= y = z = 4;
x = y == z;
1. '*=' 也是赋值运算符,赋值运算符的优先级从右至左;
x *= y = (z=4); x*= (y = 4); (x *= 4) ;
比较操作符的优先级明显高于赋值运算符,注意比较操作符返回值只有0和1;
True为1,False为0;
x = (y==z); (x = 1);
最终输出结果为:
8
1
Tips:在了解逻辑操作符之前,我们要明白“逻辑短路”;
推荐阅读:C语言逻辑运算短路
Tips:iso646.h头文件;
举个例子:
x = 1,y = 1, z = 0;
x || !y && z;
同理,优先级 !> && > || ;
绑定处理: x || (!y) && z ; x || ( (!y) && z ); (x || ( (!y) && z ));
根据逻辑短路原则 因为 x = 1,TRUE || ?,所以 || 的右边不会进行任何操作;
常考题!!!!!
切记:“++” 和 “--” 是一元操作符,关联顺序又右至左;
再举个例子:
int x = 2,y = 1,z = 0;
z += - x ++ + ++ y;
//1. z += -(x ++) + (++ y);
//++和--都只能于一个左值“绑定”;
//2. z += (-(x ++)) + (++ y);
//3. z += ((-(x ++)) + (++ y));
Obviously,结果是z = (((-2)) + (2));
z = 0;
编译器从左往右读入字符,例如 / *,那么编译器该如何读取呢?
记住一点:每一个符号应该包含尽可能多的字符。
<a +++ b; 与 a ++ + b; 含义相同;
a - - -b;与a-- - b ; 含义相同;
指针题易错点:
x = m /* n ;看似是用m除以指针n的值,实际上编译器只会将m的值赋予x;
根据“贪心法”,编译器会默认 “ /* ”为注释字符;
这里推荐菜鸟编程网站:三元运算符
条件操作符 ? :是仅次于赋值操作符优先级的操作符;
因为不常见,这里就不再赘述;
声明:经常可以看到某些OJ网站要求最后一个数后面不带空字符,我们有时也会因此煞费心机。
其实我们可以不做任何处理,判题机一样会判断正确。
最近遇到了一个很迷惑的问题:
<#include<stdio.h>int main(){char ch1;scanf("%3c",&ch1);printf("%2c",ch1);return 0;
}
输出:
第一印象告诉我,为什么不输出“ab”呢?
原来,%c只能读入单个字符; 但问题又来了,为什么“a”在中间位置呢?
我们再测试一组:
问题的答案逐渐清晰:原来,计算机只读入了字符“a”和空字符,以栈的形式缓存到内存中,打印时根据“先进后出”的原则,打印49个空字符和“a”;
#include <stdio.h>int main( )
{int c;c = getchar( );//仅能读取单个字符并返回; putchar( c );return 0;
}
int getchar(void); int putchar(int c) ;
输入:Huel
输出:H
如果想读入多个字符,可以设置while循环语句;
while ((ch = getchar()) != 'n')//当一行未结束时
{... //处理字符
}
#include <stdio.h>#define EMPTY ' '
// EMPTY 为 ‘空格’
int main(){char ch1;ch1 = getchar();// 读取一个字符;while (ch != 'n')// 当一行未结束;{if (ch == EMPTY) // 留下空格;putchar(ch); // 字符不变;elseputchar(ch + 1); ch = getchar(); //读取下一个; }putchar(ch); // 打印字符return 0;
}
Gtdk Vdhg`nq`m
Huel WeiHaoran
#include <stdio.h>int main( )
{char str[1000];//注意数组str的边界gets( str );puts( str );return 0;
}
gets()函数会无限读取直到换行符或者EOF,因此一定注意数组的边界,
一旦输入的字符大于数组长度,就会发生内存越界,从而造成程序崩溃或其他数据的错误。
输入:HuelWeihaoran
输出:HuelWeihaoran
何为常量?常量就是固定不变的值,常量可以是整形常量,浮点型常量,字符(串)常量,甚至枚举常量。
整型常量:
整数常量前缀指定:0x 或 0X 为十六进制,0 为八进制,无前缀默认为十进制。
整数常量亦可带一后缀,后缀是 U 和 L 的组合;
2333
215a
0xFeeL
//以上均为合法常量
088 //非法:8 不是八进制的数字
133LL //非法:后缀重复
字符常量:
n 换行符; \ 字符; " "字符; ' '字符;t TAB键;
何为变量?变量其实仅仅是程序可操作存储区的名称
举个栗子:
“ab” //正确,字符串常量使用“”;
‘ab’ //错误,‘’只能用于单个字符;
Ascii码表:
数字0-9对应48-57;
字母A-Z对应65-90;
字母a-z对应97-122;
定义常量:
法一:#define PI 3.1415926 #define WORD 'n'
法二:const int A = 3; const int B = 5;
基本数据类型占用空间(64位机器)
常见书写类型:
例如:%5.3lf
小数点前的数字表明输出列数,默认右对齐,加上“-”就是左对齐;
小数点后的数字表明小数点后保留位数(假如位数不够,自动补0;位数不足,四舍五入);
double x = 3.1415926;
printf("%5.3lf",x);
输出结果为:注意是四舍五入,而不是单单取小数点后三位!
3.142
举个栗子:
y = -1, x = 1;if(y>0) if(y<0) x = 3;
else x = 6;x = 10;
试问,x最后等于何值?x = 10;
根据C语言规则,else语句将被划入离它最近且可以被接受的if语句;
因此,本题中的else语句显然从属于第二个if语句;
因为第一个if语句内判断为False,所以其真支部分(if(y<0) x = 3;
else x = 6;)将不再被执行。
在这里要温习一下switch语句:
switch(表达式) {case expression1:语句1; break;case expression2:语句2; break;...default:语句; }
Tips:expression必须是整型常量或者枚举类型;
Tips:if()内的值如果是0,执行假支,非0则执行真支;
从某种程度上,循环就是递归,递归就是循环(前提是递归次数<=40次)
for循环和while循环的选择:
已经循环次数,果断选择for循环;
不知循环次数,果断选择while循环;
这里解释一下EOF;
while(scanf("%d",&num)!=EOF){}
第一,明确这是一个逻辑判断;
第二,在stdio宏定义中,发现#define EOF (-1);
第三,scanf输入时,其实输入的数值仅仅是被存放于内存缓冲区当中,只有键入回车那一刹那,以往输入的数值才会保存。如遇特殊情况,比如缓冲区文件流满,那么scanf将如何处理呢?当然是返回-1 ,因此stdio.h就专门定义一个宏来表示,取End Of File即组成EOF。
如何使用递归呢?
简单的来说,就是找到初始值和循环体;
举个例子:1,3,5,7.
初始值就是当n=1时,f(1)= 1;
循环体就是f(n)= (n-1)+2;
如何转换为C语言语句呢?
if(n=1){return 1;
}else{return f(n-1)+2;
}
模板就是:
if(初始值的下标){
return 初始值;
}else{
return 循环体;
}
需要注意的是,如果已知循环体,在不知道初始值的情况下,我们不可以确定递归函数;
例如:1,3,5,7
3, 5,7,9
递归是一个简洁的概念,同时也是一种很有用的手段。但是,使用递归是要付出代价的。与直接的语句(如while循环)相比,递归函数会耗费更多的运行时间,并且要占用大量的栈空间。递归函数每次调用自身时,都需要把它的状态存到栈中,以便在它调用完自身后,程序可以返回到它原来的状态。未经精心设计的递归函数总是会带来麻烦。 引用于:Runoob-风中追柳
推荐阅读:C语言中文网--C语言递归函数
众所周知,跳转语句包含break,goto和continue;
break:
break语句有两种用法:
1,跳出循环;2,跳出switch语句的一种case情况;
当然,如果是循环嵌套,break 语句会直接中断执行最内层的循环,即break语句只向外跳出一层
continue:
continue语句的作用是跳过循环体中剩余的语句并到循环末尾而强行执行下一次循环。
goto:
不建议使用goto语句,诚然,goto语句很有效,但goto语句会扰乱程序的逻辑性;其实,使用if-else就可以实现goto的全部功能;
1.只能由字母、数字或下划线组成,字母存在大小写;
2.第一个字符必须为字母或下划线,不能为数字;
何为标识符?主要有3类:
1.关键字标识符;2.预定义标识符;3.用户自定义标识符;
同时,用户自定义标识符不能与关键字重复;
我们突然发现,比赋值操作符优先级更低的,是逗号操作符;
逗号表达式:用逗号连接的多步操作,根据表格可知其关联规则从左往右;
举个栗子:
#include<stdio.h>int main(){int x=0,y=0;y = (x=10,x=x%7,x=x+3);//x=x%7=3,x = 3+3 = 6,y = 6;//最后()内的值为最右边表达式的值;printf("%d",y);return 0;
}
函数专题很难讲懂,除非自己真正理解;
但函数本身也不难,主要令人疑惑的就是值传递和函数声明;
函数声明问题:
#include<stdio.h>
#include<math.h>int inverse(int n);
int main(){int n,n1,res=0;scanf("%d",&n);n1 = n;res = inverse(n);printf("%d",res+n1);return 0;
}int inverse(int n){int i,k,l,num=0;k = (int)log10(n);l = (int)pow(10,k);while(n>0){num += n%10*l;n /= 10;l /= 10;}return num;}
第4行 int inverse(int n)可以写成int inverse(int m);甚至可以写成int inverse(int );
函数声明和原型参数名可以不同,编译器只消知道它的类型,与参数名无关。
函数的值传递;
#include<stdio.h>void swap(int *x, int *y);
int main()
{int a,b;a = 10;b = 20;swap(&a, &b);printf("a=%d, b=%d", a, b);return 0;
}
void swap(int *x, int *y)
{int temp;temp = *x;*x = *y;*y = temp;
}
作用域有本地变量(局部变量)和全局变量;
本地变量仅仅作用于函数内部,只有在调用该函数时本地变量才会生效;
然而,全局变量则贯穿于整个程序的运行周期中;
如果本地变量和全局变量重名,那么在函数内部会优先调用本地变量;
当然,从程序设计的角度,尽可能少的声明全局变量;
举个栗子:
在某种意义上,数组和指针密不可分,函数和指针也有着千丝万缕的关系;
本质上,数组就是一堆相同类型的变量的集合;
其实数组并不难,难的是我们自身思维方式的转变。
函数中应用数组;
void Found(int arr[],int n);
int main(){
int arr[100];
......
void Found(arr,n);
return 0;
}
只需要注意main函数内,在传入数组arr时,不能写成void Found(arr[100],n);
这样写,会让编译器认为只传入了arr数组的,下标为100的数值。
初学者学习数组推荐:
C数组-菜鸟编程
神一样的网站,包含了数组各个方面的知识点;
C语言网-数组专题
比如,这里有11道不同类型的数组题,难度不大,但非常具有代表性,可以帮助我们转变思维;
关于结构体的一些看法:
在攻克HuelOJ结构体专题时,遇到了声明结构体的困难:
参考题目:HuelOJ114,1115,1118;
For Instance:Subcripted value is neither array nor pointer nor vector;
例如:携带下标的值不是数组或者指针
解决方案:检测struct声明时是否定义了strcut数组的大小;
error:
struct com{int score;char str[20];};
Struct com com1;//没有定义struct数组,com类型,变量com1的大小;
因此,需要定义com1数组的大小;
关于结构体:
1.在一般情况下,tag、member-list、variable-list 这 3 部分至少要出现 2 个。
2.同时,struct内部:至少要对一个member - list的大小进行初始化!!!
3.当函数遇到结构体:
void printBook( struct Books book );//函数声明struct Books Book1[100]; //声明books类型的Book1数组,大小为100;printBook( Book1 ); //函数输出
#include<stdio.h>
void sort(int a[], int n);
//排序函数sort
void PrintArr(int a[],int n);
//打印函数PrintArr
int main(){int a[10];int i,j,n;scanf("%d",&n);for(i=0;i<n;i++){scanf("%d",&a[i]);}//循环将值输入到数组内部;for(j=0;j<n-1;j++){sort(a,n);}PrintArr(a,n);}
void sort(int a[], int n){int i;int temp;//设置一个“空瓶子”for(i=0;i<n;i++){if(a[i]>a[i+1]){temp = a[i];a[i] = a[i+1];a[i+1] = temp;}//遍历+排序,保证数组中每个值都可以进行比较;}
}
void PrintArr(int a[],int n){int j;for(j=0;j<n;j++){printf("%d ",a[j]);}
}
其实,sort函数也可以这样写:
在sort函数内应用两个for循环;
当然,此算法是最易理解的直接排序算法;
还有选择排序和“冒泡排序”。
本文发布于:2024-01-28 02:30:42,感谢您对本站的认可!
本文链接:https://www.4u4v.net/it/17063802484137.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
留言与评论(共有 0 条评论) |