一.熟悉C语言
1.空白和注释
(1)程序的空白就是指用空行将程序的不同部分分隔开,Tab用于缩进语句,如果我们想要写出漂亮的代码,就需要在编写程序的时候注意使得代码容易阅读,要有一个良好的编程风格。就良好 编程风格来说,大家可以看看《高质量的C C++编程》
(2)注释:注释以符号/*开始,以符号*/结束。注释不能嵌套,就是说在出现第一个/*和第一个*/之间都被看作是注释,无论中间出现多少个/*
当然,如果想要注释掉整段代码的话,就需要#if、#end if了,它们之间的程序可以有效的被注释掉,即使代码中间原先存在注释也没关系了
2.#include预处理指令可以使一个函数库头文件的内容由编译器进行处理,#define指令允许我们自己给字面值常量取符号名
3.main函数是程序执行的起点。函数的标量参数通过传值的方式进行传递,而数组名参数则具有传址调用的语义
4.一个必须注意的问题:很多初学者很容易不小心把测试相等性的符号==写成赋值符号=,这就造成程序的错误,我在初次写代码时就容易犯这个错误,希望大家不要注意
二.基本概念
1.翻译环境、执行环境
翻译过程:组成一个程序的每个源文件通过编译过程分别转换为目标代码(object code),然后,各个目标文件由链接器(Linker)捆绑在一起,形成一个单一而完整的可执行程序。如下图:
执行:程序载入到内存中,程序执行开始,调用main函数,执行程序代码,程序终止。
程序执行代码过程中,大多是机器里,①程序将使用一个运行时堆栈(stack),它用于存储函数的局部变量和返回地址。②使用静态(static)内存,存储于静态内存中的变量在程序的整个执行过程中将一直保留它们的值。
2.字符:
⑴转义序列由一个反斜杠 \ 加上一个或多个其他字符组成。每个转义序列代表反斜杠后面的那个字符,但并未给这个字符增加特别的意义。
记住:\ddd表示1~3个八进制数字
\xddd表示十六进制数字
3.标识符:变量、函数、类型等的名字。由大小写字母、数字和下划线组成,但是不能以数字开头。
下列关键字不能作为标识符使用:
auto do goto signed unsigned break double
if sizeof void case wlse int volatile
static
enum long struct while const extern
register switch continue float return char
下面通过写一个小程序来练习一下C程序:
编写一个程序,它从标准输入读取C源代码,并验证所有的花括号都正确地成对出现。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
#include<stdio.h>
#include<stdlib.h>
int main()
{
int ch;
int count=0;
while ((ch= getchar ())!=EOF)
{
if (ch == '{' )
count+=1;
if (ch == '}' )
{
if (count == 0)
printf ( "匹配失败!\n" );
else
count-=1;
}
}
if (count>0)
printf ( "匹配成功!\n" );
system ( "pause" );
return 0;
}
|
三.数据
1.基本数据类型
⑴整型
整型值大小规定:长整型至少应该和整型一样长,而整型至少应该和短整型一样长
变量的最小范围
类型 | 最小范围 |
char | 0~127 |
signed char | -127~127 |
unsigned char | 0~255 |
short int | -32767~32767 |
unsigned short int | 0~65535 |
int | -32767~32767 |
unsigned int | 0~65535 |
long int | -2147483647~2147483647 |
unsigned long int | 0~4294967295 |
整型字面值:指定了自身的值,并且不允许发生改变
与普通变量的区别:被初始化以后,其值不能被改变
书写:
十进制整型值: 123 65535 (缺省情况下是最短类型)
八进制或十六进制 : 0173 017777 0x7b
枚举类型:值为符号常量而不是字面值的类型
这种类型的变量实际上以整型的方式存储,符号名的实际值都是整型值
①适当时候为符号名指定特定的整型值:
enum Jar_Type{CUP = 8, PINT = 16};
②如果某个符号名未显示指定一个值,那么它的值就比前面一个符号名的值大1
⑵浮点类型:
ANSI标准规定long double 至少和double一样长,而double至少和float一样长。同时规定了一个最小范围:所有浮
点类型至少能够容纳从10^-37到10^37之间的任何值
浮点数的形式存储:
对于浮点类型的数据采用单精度类型(float)和双精度类型(double)来存储,float数据占用32bit,double数据占用64bit,我们在声明一个变量float f= 2.25f的时候,是如何分配内存的呢?如果胡乱分配,那世界岂不是乱套了么,其实不论是float还是double在存储方式上都是遵从IEEE的规范的,float遵从的是IEEE R32.24 ,而double 遵从的是R64.53。
无论是单精度还是双精度在存储中都分为三个部分:
-
符号位(Sign) : 0代表正,1代表为负
-
指数位(Exponent):用于存储科学计数法中的指数数据,并且采用移位存储
-
尾数部分(Mantissa):尾数部分
其中float的存储方式如下图所示:
而双精度的存储方式为:
R32.24和R64.53的存储方式都是用科学计数法来存储数据的,比如8.25用十进制的科学计数法表示就为:8.25*,而120.5可以表示为:1.205*,这些小学的知识就不用多说了吧。而我们傻蛋计算机根本不认识十进制的数据,他只认识0,1,所以在计算机存储中,首先要将上面的数更改为二进制的科学计数法表示,8.25用二进制表示可表示为1000.01,我靠,不会连这都不会转换吧?那我估计要没辙了。120.5用二进制表示为:1110110.1用二进制的科学计数法表示1000.01可以表示为1.0001*,1110110.1可以表示为1.1101101*,任何一个数都的科学计数法表示都为1.xxx*,尾数部分就可以表示为xxxx,第一位都是1嘛,干嘛还要表示呀?可以将小数点前面的1省略,所以23bit的尾数部分,可以表示的精度却变成了24bit,道理就是在这里,那24bit能精确到小数点后几位呢,我们知道9的二进制表示为1001,所以4bit能精确十进制中的1位小数点,24bit就能使float能精确到小数点后6位,而对于指数部分,因为指数可正可负,8位的指数位能表示的指数范围就应该为:-127-128了,所以指数部分的存储采用移位存储,存储的数据为元数据+127,下面就看看8.25和120.5在内存中真正的存储方式。
首先看下8.25,用二进制的科学计数法表示为:1.0001*
按照上面的存储方式,符号位为:0,表示为正,指数位为:3+127=130 ,位数部分为,故8.25的存储方式如下图所示:
而单精度浮点数120.5的存储方式如下图所示:
那么如果给出内存中一段数据,并且告诉你是单精度存储的话,你如何知道该数据的十进制数值呢?其实就是对上面的反推过程,比如给出如下内存数据:0100001011101101000000000000,首先我们现将该数据分段,0 10000 0101 110 1101 0000 0000 0000 0000,在内存中的存储就为下图所示:
根据我们的计算方式,可以计算出,这样一组数据表示为:1.1101101*=120.5
而双精度浮点数的存储和单精度的存储大同小异,不同的是指数部分和尾数部分的位数。所以这里不再详细的介绍双精度的存储方式了,只将120.5的最后存储方式图给出,大家可以仔细想想为何是这样子的
下面我就这个基础知识点来解决一个我们的一个疑惑,请看下面一段程序,注意观察输出结果
float f = 2.2f;
double d = (double)f;
Console.WriteLine(d.ToString("0.0000000000000"));
f = 2.25f;
d = (double)f;
Console.WriteLine(d.ToString("0.0000000000000"));
可能输出的结果让大家疑惑不解,单精度的2.2转换为双精度后,精确到小数点后13位后变为了2.2000000476837,而单精度的2.25转换为双精度后,变为了2.2500000000000,为何2.2在转换后的数值更改了而2.25却没有更改呢?很奇怪吧?其实通过上面关于两种存储结果的介绍,我们已经大概能找到答案。首先我们看看2.25的单精度存储方式,很简单 0 1000 0001 001 0000 0000 0000 0000 0000,而2.25的双精度表示为:0 100 0000 0001 0010 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000,这样2.25在进行强制转换的时候,数值是不会变的,而我们再看看2.2呢,2.2用科学计数法表示应该为:将十进制的小数转换为二进制的小数的方法为将小数*2,取整数部分,所以0.282=0.4,所以二进制小数第一位为0.4的整数部分0,0.4×2=0.8,第二位为0,0.8*2=1.6,第三位为1,0.6×2 = 1.2,第四位为1,0.2*2=0.4,第五位为0,这样永远也不可能乘到=1.0,得到的二进制是一个无限循环的排列 00110011001100110011... ,对于单精度数据来说,尾数只能表示24bit的精度,所以2.2的float存储为:
但是这样存储方式,换算成十进制的值,却不会是2.2的,应为十进制在转换为二进制的时候可能会不准确,如2.2,而double类型的数据也存在同样的问题,所以在浮点数表示中会产生些许的误差,在单精度转换为双精度的时候,也会存在误差的问题,对于能够用二进制表示的十进制数据,如2.25,这个误差就会不存在,所以会出现上面比较奇怪的输出结果。
⑶指针
字符串常量
字符串常量可以为空,但即使是空字符串,依然存在作为终止符的NILL字节。
在程序中使用字符串常量会生成一个“指向字符的常量指针”。当一个字符串常量出现于一个表达式中时,表达式所使用的值就是这些字符所存储的地址,而不是这些字符本身。因此,我们可以把字符串常量赋值给一个“指向字符的指针”,后者指向这些字符所存储的地址。但是,不能把字符串常量赋值给一个字符数组,因为字符串常量的直接值是一个指针,而不是这些字符本身。
2.基本声明
例:int i;
char j,k,l;
初始化: int j = 24;
声明数组: int value[20];
声明指针:int *a;这句表示表达式*a产生的结果类型是int。
注意:如果上例写成int* a ,a被声明为int*的指针。然而这在一些声明中容易造成错误。比如
int* b, c, d;
人们会自然的认为这是把三个变量声明为指向整型的指针,事实并非如此。*实际上只是*b的一部分,b是一个指针,c,d只是普通的整型。正确的应该是
int *b, *c, *d;
3.常量
使用关键字const来声明常量
1
2
3
4
|
int *pi;
int const *pi;
int * const cpi;
int const * const cpci;
|
const变量只能用于允许使用变量的地方
4.作用域
代码块作用域(代码块开始处;嵌套时不要有同样的变量名)
文件作用域
原型作用域
函数作用域(函数中的所有语句标签必须唯一)