第一关:
整型常量有三种表示方法:
1 十进制整数
2 八进制整数:以0开头的数,如0376是八进制数
3 十六进制整数:以0x开头的数,如0×4a3e是十六进制数
第二关:
一个正数的补码和其原码相同。
求负数的补码的方法是:将该数的绝对值的二进制形式,按位取反加1。
如-10的16位二进制形式为0000000000001010,按位取反后为1111111111110101,再加1为1111111111110110
第三关:
一个16位的整数的表示范围为:-32768~32767
你应该知道原因。因为0000000000000000表示0,而1000000000000000表示-32768
第四关:
如果要表示大于32767的整数,则需要在printf里用%ld,而不能再用%d了,因为%d只能表示最大到32767
第五关:
一个整常量后面加上u或U,就表示此常量为无符号数。如12345u或12345U,则计算机就会在内存里按unsigned int规定的方式来存放。
如果是-12345u,那么计算机会先将-12345转换为其补码形式53191,然后再按无符号数形式存放。
如果一个整常量后面加字母l或L,则表示为long int型常量。
第六关:
当计算机遇到一个小数,则会先将它转换成一个双精度数据存储(64位),虽然提高了精度,但这样运算速度就会降低。所以如果我们更注重运算速度,可以在小数后面加上f或F,来告诉计算机按单精度处理,不要转换为双精度数。
第七关:
在不同类型数据的混合运算中,有一些隐蔽的规则:
1 char和short是必须先转换为int才可以进行运算的。
2 float一律转换为双精度型再进行运算,即使是两个float型数据相加,亦是如此。
3 如果int和double进行运算,那么int先转换成double,然后两个double进行运算
4 如果int和unsigned int进行运算,那么int要先转换为unsigned int,然后两个unsigned int进行运算
5 这些类型转换是由系统自动完成的。
第八关:
在强制类型转换时,得到的是一个所需类型的中间变量,原来变量的类型不会发生变化,如(double)(x),x的类型本身不会变化。
(后加的)第八点五关:
单目运算符包括:!,~,++,–,-,(type),*,&,sizeof
单目运算符的优先级仅次于(),[],->,.这四个(并非真正意义上的)运算符。
单目运算符的结合性是自右向左。如-i++,即-(i++);如*i++,即*(i++)。
第九关:
C编译系统会使用贪心法来识别运算符,如 i+++j,会被理解为(i++)+j
第十关:
赋值运算符的结合性也是“自右向左”。如a=b=5,即a=(b=5)
在C语言中,直接声明int a=b=c=3是错误的,但如果先声明int b;int c;,再声明int a=b=c=3;就是正确的!
第十一关:
逗号表达式形如:表达式1,表达式2
逗号表达式的值是表达式2的值。
由于赋值运算符的优先级高于逗号运算符,所以a=3*5,a*4被理解为(a=3*5),a*4
逗号运算符基本上是优先级最最低的一个运算符,而赋值运算符基本上是排在倒数第二。(逗号全班倒数第一。赋值全班倒数第二,呵呵),你一定想知道倒数第三是谁,告诉你,条件运算符,也就是C中唯一的三目运算符”?:”。
第十二关:
a=3叫做赋值表达式,而a=3;叫做赋值语句,对,就是因为多了一个分号。
只有一个分号也能构成一个语句,叫做“空语句”。
如果用{}把一堆语句括起来,那么就叫复合语句,也叫分程序。
第十三关:
C语言提供的关系运算符有6种,你先想想,再看答案:
<, <=, >, >=, ==, !=,其中前四种的优先级要高于后两种。
C语言提供3种逻辑运算符,你知道么:
&&,||,!,这三种优先级各不相同,!优先级最高,&&次之,||优先级最低。而且更复杂的是,&&和||的优先级低于关系运算符,而!高于算术算符
总结起来就是:
逗号运算符<赋值运算符<||<&&<关系运算符<算术运算符<!
第十四关:
具有自右向左结合性的运算符都包括:赋值运算符,单目运算符和条件运算符。如a>b?a:c>d?c:d,即a>b?a:(c>d?c:d)
第十五关:
switch语句的语法结构:
switch(表达式)
{
case 常量表达式1:语句1
case 常量表达式2:语句2
…
case 常量表达式n:语句n
default:语句n+1
}
各个case和default的出现次序不影响执行结果,你愿意的话,完全可以先出现default,再出现其他case。
第十六关:
for(表达式1;表达式2;表达式3){…}
for语句,如果省略表达式1和表达式3,只留表达式2,那么相当于while语句。
for(;;)相当于while(1).
巧用for语句的一个例子:for(i=0;(c=getchar()!=’/n’);i+=c);
第十七关:
C语言中,二维数组中元素排列的顺序是:按行存放,即在内存中先顺序存放第一行的元素,再顺序存放第二行的元素…
多维数组在内存中的排列顺序是,第一维的下标变化最慢,最右边的下标变化最快。如a[0][0][0]->a[0][0][1]->a[0][0][2]->a[0][0][3]->a[0][1][0]…
二维数组的初始化:
int a[3][4]={{1,2,3,4},{5,6,7,8},{9,10,11,12}}; (推荐)
相当于:
int a[3][4]={1,2,3,4,5,6,7,8,9,10,11,12}; (不推荐,因为界限不清晰)
int a[3][4]={{1},{5},{9}};只对各行第1列的元素赋初值,其余元素都为0.
如果对全部元素都赋初值,则定义数组时对第一维的长度可以不指定,但第二维的长度不能省略,如:
int a[][4]={1,2,3,4,5,6,7,8,9,10,11,12};
在定义时也可以只对部分元素赋值而省略第一维长度,但必须分行赋初值:
int a[][4]={{0,0,3},{},{0,10}};
第十八关:
字符数组初始化,可以加花括号,也可不加:
char c[]={“I am happy”};
等价于:
char c[]=”I am happy”;
等价于:
char c[]={‘I’,’ ‘,’a',’m',’ ‘,’h',’a',’p',’p',’y',’/0′}; 务必加上最后的’/0′,否则可不等价哦。
第十九关:
scanf函数会以空格来作为结束符。所以scanf(“%s”,str)的话,
如果输入是how are you? 那么最后str中存入的只有how。这点要特别注意。
第二十关:
定义函数时,有传统方式和现代方式。
现代方式:
int max(int x,int y){
…
}
传统方式:
int max(x,y)
int x;
int y;
{
…
}
第二十一关:
函数的定义和声明不是一回事。
定义是对函数功能的确立,包括指定函数名、函数值类型、形参以及其类型、函数体等,它是一个完整的、独立的函数单位。
声明的作用是把函数的名字、函数的类型以及形参的类型、个数和顺序通知编译系统,以便在调用该函数时系统按此进行对照检查。
在函数的声明中完全可以不写形参名,而只写形参的类型。如:float add(float,float);
旧版本的C语言中,函数声明可以只声明函数名和函数返回类型,如int add();
如果在函数调用之前,没有对函数做声明,则编译系统会把第一次遇到的该函数形式作为函数的声明,并将函数返回类型默认为int。所以如下的程序,虽然没有事先声明max函数,但仍可以侥幸通过:
int main()
{
int c=max(2,5);
}
max(int x,int y)
{
int z;
z=x>y?x:y;
return z;
}
可见,没有定义函数类型(即函数返回类型)的函数,都会默认为int型。
上述程序能侥幸通过的原因是return z中的z是int型。如果z是其他任何类型,程序都会报错,即使是相兼容的short、long、char或unsigned int都不行!
第二十二关:
形参数组可以不指定大小,且在形参中int a[]和int a[131212]和int *a是等价的!
形参多维数组的话,只能省略第一维的数组,其他维必须按照正确的值来填写。
第二十三关:
用户存储空间包括:
(1)程序区
(2)静态存储区
(3)动态存储区
全局变量全部放在静态存储区。
动态存储区存放以下数据:
1 函数形参
2 自动变量,即未使用static声明的局部变量
3 函数调用的现场保存和返回地址
在C语言中,每个变量有两个属性:
1 数据类型
2 数据存储类型
第二十四关:
函数中的局部变量,如果不专门声明为static存储类型,则默认都为auto存储类别,都会被分配到动态存储区。auto是一个默认关键字。即int a;等价于auto int a;
静态存储类别的变量,被分配在静态存储区,在程序整个运行期间都不释放。静态局部变量在编译时赋初值,且只赋初值一次,如果用户不指定,则为0。(auto类型变量的话,如果用户不指定初值,则初值是一个不确定的值)
第二十五关:
为了提高效率,C语言允许将局部变量放在CPU中的寄存器里,这种变量叫做“寄存器变量”,如register int a;
只有局部自动变量和形参变量可以作为寄存器变量。
局部静态变量不能作为寄存器变量。
第二十六关:
extern用于声明外部变量,如extern char a,b;
注意,除非引用的外部变量是int型,否则必须写上数据类型,而且和外部变量的数据类型必须完全一致。
第二十七关:
使用#define定义的宏,可以用#undef来取消。
如#define G 9.8,可以用如下命令取消:
#undef G
C程序中,用双引号括起来的部分,不会被宏所代替。
定义计算圆面积的宏,应该注意要加上括号,即:
#define PI 3.14
#define S(r) PI*(r)*(r)
宏名是无类型的,而且它的参数也是没有类型的。
宏替换不占运行时间,只占编译时间。
第二十八关:
头文件,使用.h后缀只是为了表明文件性质。所以,即使没有.h也是没问题的。
头文件里应该可以函数原型、宏定义、结构体类型定义以及全局变量定义。
第二十九关:
&和*的优先级相同,且结合性是自右至左。(单目运算符的结合性是右结合)
*pointer++,由于++和*为同一优先级,而结合性又是自右至左,所以相当与*(pointer++)
第三十关:
使用指针来引用数组,可以提高目标程序质量(占内存少,运行速度快)。
main()
{
int a[10];
int *p,i;
for(i=0;i<10;i++)
scanf(“%d”,&a[i]);
printf(“/n”);
for(p=a;p<(a+10);p++)
printf(“%d”,*p);
}
此程序会提高程序运行效率,因为像p++这样的指针操作是比较快的。
数据名是数组首元素的地址,它是一个指针常量。它的值在程序运行期间是不变的。
第三十一关:
当在形参中要引用一个数组时,往往C语言专业人员喜欢用指针变量做形参。如,专业人员
更喜欢f(int *arr,int n);
不太喜欢f(int arr[], int n);
第三十二关:
对于int a[3][4]={{1,3,5,7},{9,11,13,15},{17,19,21,23}};
a[0],a[1],a[2],每一个元素又是一个一维数组。
a+1代表第一行的首地址,而a+1就代表第二行的首地址。
*(a[i]+j)和*(*(a+i)+j)是二维数组元素a[i][j]的值。
a和a[0]的值虽然相同,但其指针类型不同,a指向一维数组,a[0]指向a[0][0].
第三十三关:
int (*p)[4],p是一个指针变量,它指向包含4个整型元素的一维数组。注意*p两侧的括号不能缺少,因为[]的优先级高于*,所以如果写成*p[4],那就变成了*(p[4]),这个表示一个含有4个元素的一维数组,其中每个元素都是一个指针变量。
第三十四关:
用字符指针做形参来实现字符串的复制:
void copy_string(char *from, char *to)
{
for(;*from!=’/0′;from++,to++) *to=*from;
*to=’/0′;
}
此代码可以改进为:
void copy_string(char *from,char *to)
{
while((*to++=*from++)!=’/0′);
}
此代码还可以改进,由于’/0′可以用0代替,且条件里0即代表假,所以代码可修改为:
void copy_string(char *from,char *to)
{
while(*to++=*from++);
}
对于C语言熟练之后,这种形式的使用是非常多的,读者应该逐渐熟悉它,掌握它。
第三十五关:
char *a;
scanf(“%s”,a);
上述程序是绝对禁止的,是错误的。正确的写法如下:
char *a,str[10];
a=str;
scanf(“%s”,a);
第三十六关:
用指针变量来指向一个格式字符串,可以用它来代替printf函数中的格式字符串,如:
char *format;
format=”a=%d,b=%f/n”;
printf(format,a,b);
其实format也可以定义为char format[]形式,但不建议,因为用指针的话,可以很方便指向另一个格式字符串。
第三十七关:
声明一个指向函数的指针:int (*p)(); 定义p是一个指向函数的指针变量,此函数带回整型的返回值。
所以定义指向函数的指针时,只需要注意返回值相匹配即可,而形参部分不需要考虑。
注意,如果写成int *p();由于()的优先级高于*,就变成了声明一个函数,它的返回值是指向整型的指针。
使用p的方法:
int max(int a, int b);
p=max;
num=(*p)(3,5);
在一个程序中,一个指针变量可以先后指向返回类型相同的不同函数。
第三十八关:
用指向函数的指针做函数参数:
sub(int (*x1)(int),int (*x2)(int,int));
这里的参数列表可以选择不写(不推荐此种写法),即sub(int (*x1)(),int (*x2)());,不过如果写参数,就必须要写全写正确。
虽然C允许在参数中用()省略掉形参表,但是,从一个良好的程序风格来看,还是应当禁止这样的使用。
第三十九关:
指针数组的定义:
char *name[]={“follow me”,”basic”,”great wall”,”fortran”,”computer”};
name代表该指针数组的首地址,name+i是name[i]的地址。
程序举例:
char *name[]={“follow me”,”basic”,”great wall”,”fortran”,”computer”};
char **p;
int i;
for (i=0;i<5;i++){
p=name+i;
printf(“%s/n”,*p);
}
第四十关:
struct定义一定不要忘了最后的分号:
struct 结构体名
{成员列表};
在引用结构体变量的域时,*p.num相当于*(p.num),因为.的优先级是最高的!
第四十一关:
共用体的定义为:
union 共用体名称
{
成员列表;
}变量列表;
例如:
union data
{
int i;
char ch;
float f;
}a,b,c;
共用体变量所占的内存长度是最长的成员的长度。
在引用共用体变量时应十分注意当前存放在共用体变量中的究竟是哪一个成员。
&a、&a.i、&a.ch、&a.f都是同一个地址值。
不能对共用体变量名赋值,也不能企图引用变量名得到一个值,更不能在定义共用体时对它初始化。
第四十二关:
声明枚举类型的举例:
enum weekday{sun,mon,tue,wed,thu,fri,sat};
声明了枚举类型之后,就可以用枚举类型来定义变量:
enum weekday workday,weekend;
对于枚举元素,C语言编译时的顺序使它们的值为0,1,2…
如
workday=mon;
printf(“%d”,workday);
则会显示1
当然你可以改变这种现状:
enum weekday{sun=7,mon=1,tue,wed,thu,fri,sat}workday,weekend;
此时,sun=7,mon=1,tue=2…以此类推
一个整数不能直接赋值给一个枚举变量,而要先进行类型转换:
workday=(enum workday)2;
第四十三关:
用typedef声明结构体类型:
typedef struct
{
int month;
int day;
int year;
}DATE;
这样就声明了新的类型DATE,然后就可以用DATE来声明结构体变量了。如DATE birthday;DATE *p;
用typedef声明数组类型:
typedef int NUM[100];
然后就可以用NUM来定义数组变量:
NUM n;
第四十四关:
C语言的位运算有6个运算符:
1 与&
2 或|
3 异或^
4 取反~
5 左移<<
6 右移>>
位运算的运算量只能是整型或字符型,不能是其他类型,否则会报错。
按位与:用于清零或提取某一位
按位或:用于置1
异或:用于交换两个整型值或两个字符型值。如a=a^b;b=b^a;a=a^b;
取反:令最低位置1。如a=a&~1。此方法适用于在32位和64位机之间兼容。
左移:相当于乘以2,右补0。
右移:如果首位为0,则左补0;如果首位为1,则分为逻辑右移和算术右移两种情况。
逻辑右移:左补0
算术右移:左补1
第四十五关:
位段的概念非常重要,在编写网络程序常会用到。
struct packet_data
{
unsigned a:2;
unsigned b:6;
unsigned c:4;
unsigned d:4;
int i;
}data;
当然也可以不恰好占满一个字节,如
struct packet_data
{
unsigned a:2;
unsigned b:3;
unsigned c:4;
int i;
}data;
这样的话a,b,c会占去2字节中的前9位,而后7位会空闲下来,而i会从另一个新字节开头开始。
在引用位域时,要特别注意其最大值范围,如占2位,那么最大值为3.
位段成员的类型只能指定为unsigned int或int型。
若要强制一个域从新字节开始,那么可以这样:
unsigned a:1;
unsigned b:2;
unsigned :0;
unsigned c:3;
此时c会从一个新字节开始存储。
位段可以用%d来输出。
可以定义无名位段,表示这些位我不用:
unsigned a:1;
unsigned :2; //这两位空间我不用
unsigned c:3;
第四十六关:
在缓冲文件系统中,有一个概念叫做“文件指针”。
在stdio.h中有关FILE结构体类型的声明:
typedef struct
{
short level; //缓冲区满或空的程度
unsigned flags; //文件状态标志
char fd; //文件描述符
unsigned char hold; //如无缓冲区不读取字符
short bsize; //缓冲区的大小
unsigned char *buffer; //数据缓冲区的位置
unsigned char *curp; //指针、当前的指向
unsigned istemp; //临时文件,指示器
short token; //用于有效性检查
}FILE;
可见,其实FILE也是一个由typedef定义过的类型,其本质是一个结构体类型,其中有很多域,存储着和这个文件相关的各种各样的信息。
在进行文件读写操作时,建议不要使用fprintf和fscanf函数,而尽量使用fread和fwrite函数。
引用:
“第三十八关:
用指向函数的指针做函数参数:
sub(int (*x1)(int),int (*x2)(int,int));
其实,此处的参数规格完全可以乱写或者不屑,因为函数指针只是接受函数的首地址,和后面的参数啥的都没关系。”
此处不仅不正确,而且严重误导。
1. 指针也是有类型的,不要以为指针就是一个地址值而已。传入非正确类型的参数将会导致程序行为异常。
[leemars@LeeMaRS-Notebook shm]$ cat test.c
#include
void funa(int (*f1)(int, int), int (*f2)(int)) {
printf(“%d/n”, (*f1)(3, 4));
printf(“%d/n”, (*f2)(5));
}
int funb(int p1) {
return -p1;
};
int func(int p1, int p2) {
return p1 + p2;
}
int main() {
funa(funb, func);
funa(func, funb);
return 0;
}
[leemars@LeeMaRS-Notebook shm]$ gcc test.c
test.c: In function ‘main’:
test.c:17: warning: passing argument 1 of ‘funa’ from incompatible pointer type
test.c:17: warning: passing argument 2 of ‘funa’ from incompatible pointer type
[leemars@LeeMaRS-Notebook shm]$ ./a.out
-3
2
7
-5
可以看到如果不按类型传参数,将会得到警告,同时程序执行的结果错误。
2. 进一步,在参数的声明决定了在函数中调用时的函数规格,乱定义将直接导致编译不通过。
[leemars@LeeMaRS-Notebook shm]$ vim test.c
[leemars@LeeMaRS-Notebook shm]$ cat test.c
#include
void funa(int (*f1)(int), int (*f2)(int)) {
printf(“%d/n”, (*f1)(3, 4));
printf(“%d/n”, (*f2)(5));
}
int funb(int p1) {
return -p1;
};
int func(int p1, int p2) {
return p1 + p2;
}
int main() {
funa(funb, func);
funa(func, funb);
return 0;
}
[leemars@LeeMaRS-Notebook shm]$ gcc test.c
test.c: In function ‘funa’:
test.c:4: error: too many arguments to function ‘f1’
test.c: In function ‘main’:
test.c:17: warning: passing argument 2 of ‘funa’ from incompatible pointer type
test.c:18: warning: passing argument 1 of ‘funa’ from incompatible pointer type
3. 程序员一定要养成良好的编程习惯,否则将给别人给自己带来数不清的麻烦
c总结 45关
最新推荐文章于 2022-07-19 10:09:12 发布