C语言是一门最基础的语言,也是一门广泛使用的语言。所以学习C语言义不容辞。通过这段时间的学习,现将我的学习心得说明如下。
(一) 学习一门语言,就是要按照它的语法来编程。要编程,当然就要上机操作来验证你程序的正确性。所以上机非常重要,通过Compile(Ctrl+F7),Build(F7),Execute program(Ctrl+F5)等步骤来执行你所写的程序,完成你所写程序的目标,最终达到你的目的。而且通过执行程序,你会发现程序中的错误,从而使你了解你所学知识中的不足,同时,要学会根据编译时提示的错误来改正程序中发生的错误,以便下次不会再犯同样的错误。。
(二) C语言的语法部分。这部分内容是学好C语言的基础,只有学好了这些语法,才会写程序,看程序。所以对一个初学者来说,这部分内容还是挺重要的,所以要扎实地熟悉每一个语法,并能根据这些语法来编程。但如果有学过C语言,或是以前有学过其他语言,那么学习这部分应该没有很大问题。 所以在此就不多说了。
(三) 现在就让我来说说学习C语言的重点,难点,以及易出错的地方。当然,这只是针对我个人而言。在C语言学习过程,重点就是数组,指针,以及结构体了。难点当然也是它们了,不过数组和结构体还是蛮简单,所以在此主要就指针方面谈谈自己的看法。
指针:(C语言中的精华)
1 概念指针其实就是一个变量的地址。都知道如果在程序中定义了一个变量,那么在对程序进行编译时,系统就会给这个变量分配内存单元。这个内存单元就是这个变量的地址。刚已说过,指针其实就是一个变量的地址。则指针其实就是通过这个地址指向这个变量。也就是说,指针是一个地址,而指针变量是存放地址的变量。
2 定义定义一个指针变量:基类型 *指针变量名;如float *p1; //p1是指向float型变量的指针变量注意:p1只能指向float型的,不能指向int 或char型。 int a ; float *p1; p1=&a; //是不行的。
3 指针的作用(指针变量作为函数参数)现来比较一下下面两个程序: //此程序是正确的。可以达到交换两个数的目的。swap(int *p1,int *p2)其实是交换两个变量(a和b)的值。所以可以达到目的。 #include <stdio.h> swap(int *p1,int *p2) {int temp; temp=*p1; *p1=*p2; *p2=temp; } void main() { int a,b; int *pointer_1,*pointer_2; scanf("%d,%d",&a,&b); pointer_1=&a;pointer_2=&b; if(a<b) swap(pointer_1,pointer_2); printf("/n%d,%d/n",a,b); } //而这个程序没有达到交换的目的。因为改变指针形参的值并不能使指针实参的值改变。 #include <stdio.h> swap(int *p1,int *p2) {int *p; p=p1; p1=p2; p2=p; } void main() { int a,b; int *pointer_1,*pointer_2; scanf("%d,%d",&a,&b); pointer_1=&a;pointer_2=&b; if(a<b) swap(pointer_1,pointer_2); printf("/n%d,%d/n",*pointer_1,*pointer_2); } 应特别注意:不能企图通过改变指针形参的值而使指针实参的值改变。
4 引用一个数组元素,可以用下标法,也可以用指针法如 int a[10]; int *p; p=a; 则要输出该数组第i 个元素,用*(a+i)、*(p+i)、a[i]都可以。
5 归纳:如果有一个实参数组,要想在函数中改变此数组中的元素的值,实参与形参的对应关系有4种情况: ①形参和实参都用数组名 ②实参用数组名,形参用指针变量 ③实参形参都用指针变量 ④实参为指针变量,形参为数组名
6 多维数组C语言允许把一个二维数组分解为多个一维数组来处理。现通过一个详细的例子来说明。
int a[3][4]={{0,1,2,3},{4,5,6,7},{8,9,10,11}} //则数组a可分解为三个一维数组,即a[0],a[1],a[2]。每一个一维数组又含有四个元素。如a[0]数组,含有a[0][0],a[0][1],a[0][2],a[0][3]四个元素。假设数组a 的首地址是1000.则数组及数组元素的地址表示如下:从二维数组的角度来看,a是二维数组名,a代表整个二维数组的首地址,也是二维数组0行的首地址,等于1000。a+1代表第一行的首地址,等于1008。 a[0]是第一个一维数组的数组名和首地址,因此也为1000。*(a+0)或*a是与a[0]等效的,它表示一维数组a[0]0 号元素的首地址,也为1000。&a[0][0]是二维数组a的0行0列元素首地址,同样是1000。因此,a,a[0],*(a+0),*a,&a[0][0]是相等的。同理,a+1是二维数组1行的首地址,等于1008。a[1]是第二个一维数组的数组名和首地址,因此也为1008。&a[1][0]是二维数组a的1行0列元素地址,也是1008。因此a+1,a[1],*(a+1),&a[1][0]是等同的。由此可得出:a+i,a[i],*(a+i),&a[i][0]是等同的。此外,&a[i]和a[i]也是等同的。因为在二维数组中不能把&a[i]理解为元素a[i]的地址,不存在元素a[i]。C语言规定,它是一种地址计算方法,表示数组a第i行首地址。由此,我们得出:a[i],&a[i],*(a+i)和a+i也都是等同的。另外,a[0]也可以看成是a[0]+0,是一维数组a[0]的0号元素的首地址,而a[0]+1则是a[0]的1号元素首地址,由此可得出a[i]+j则是一维数组a[i]的j号元素首地址,它等于&a[i][j]。由a[i]=*(a+i)得a[i]+j=*(a+i)+j。由于*(a+i)+j是二维数组a的i行j列元素的首地址,所以,该元素的值等于*(*(a+i)+j)。应特别注意它们之间的区别,理解到底是代表地址还是数值。。
7 注意使用二维数组指针变量和指针数组的区别指向多维数组的指针变量把二维数组a分解为一维数组a[0],a[1],a[2]之后,设p为指向二维数组的指针变量。可定义为: int (*p)[4] 它表示p是一个指针变量,它指向包含4个元素的一维数组。若指向第一个一维数组a[0],其值等于a,a[0],或&a[0][0]等。而p+i则指向一维数组a[i]。从前面的分析可得出*(p+i)+j是二维数组i行j 列的元素的地址,而*(*(p+i)+j)则是i行j列元素的值 注意:int (*p) [4] 二维数组指针变量 而int *p [4] 则是一个指针数组。 指针数组如int *p [4],它表示p是一个指针数组,它有三个数组元素,每个元素值都是一个指针,指向整型变量。
8 注意:使用字符串指针变量与字符数组的区别用字符数组和字符指针变量都可实现字符串的存储和运算。但是两者是有区别的。在使用时应注意以下几个问题:
1. 字符串指针变量本身是一个变量,用于存放字符串的首地址。而字符串本身是存放在以该首地址为首的一块连续的内存空间中并以‘/0’作为串的结束。字符数组是由于若干个数组元素组成的,它可用来存放整个字符串。 2. 对字符串指针方式 char *ps="C Language"; 可以写为: char *ps; ps="C Language"; 而对数组方式: static char st[]={"C Language"}; 不能写为: char st[20]; st={"C Language"}; 而只能对字符数组的各元素逐个赋值。从以上几点可以看出字符串指针变量与字符数组在使用时的区别,同时也可看出使用指针变量更加方便。
9 下面要注意区分函数指针变量和指针型函数的不同:函数指针变量C语言中,一个函数总是占用一段连续的内存区,而函数名就是该函数所占内存区的首地址。我们可以把函数的这个首地址(或称入口地址)赋予一个指针变量,使该指针变量指向该函数。然后通过指针变量就可以找到并调用这个函数。我们把这种指向函数的指针变量称为“函数指针变量”。函数指针变量定义的一般形式为:类型说明符 (*指针变量名)();其中“类型说明符”表示被指函数的返回值的类型。“(* 指针变量名)”表示“*”后面的变量是定义的指针变量。最后的空括号表示指针变量所指的是一个函数。如:int (*pf)();表示pf是一个指向函数入口的指针变量,该函数的返回值(函数值)是整型。调用函数的一般形式为:(*指针变量名)(实参表); 指针型函数定义指针型函数的一般形式为:类型说明符 *函数名(形参表) { /*函数体*/ } 其中函数名之前加了“*”号表明这是一个指针型函数,即返回值是一个指针。类型说明符表示了返回的指针值所指向的数据类型。
应该特别注意的是函数指针变量和指针型函数这两者在写法和意义上的区别。如int(*p)()和int *p()是两个完全不同的量。 int (*p)()是一个变量说明,说明p是一个指向函数入口的指针变量,该函数的返回值是整型量,(*p)的两边的括号不能少。 int *p()则不是变量说明而是函数说明,说明p是一个指针型函数,其返回值是一个指向整型量的指针,*p两边没有括号。作为函数说明,在括号内最好写入形式参数,这样便于与变量说明区别。对于指针型函数定义,int *p()只是函数头部分,一般还应该有函数体部分。
10 指向指针的指针定义:如果一个指针变量存放的又是另一个指针变量的地址,则称这个指针变量为指向指针的指针变量。如 char **p; p前面有两个*号,相当于*(*p)。显然*p是指针变量的定义形式,如果没有最前面的*,那就是定义了一个指向字符数据的指针变量。现在它前面又有一个*号,表示指针变量p是指向一个字符指针型变量的。*p就是p所指向的另一个指针变量。
总结:使用指针时要特别小心,一不小心,错误就会在你不注意时形成。所以要慎用指针。千万不能疏忽。要深刻理解它们之间的区别与联系。在很多使用指针的地方,大都会用到数组,所以要注意指针和数组的区别和联系。尤其在值传递时,形参和实参的值的使用。
位运算由于C语言允许直接访问物理地址,能进行位(bit)操作,能实现汇编语言的大部分功能,可以直接对硬件进行操作。所以现在就让我来谈谈C语言中的位运算。
C语言提供了六种位运算符: & 按位与 | 按位或 ^ 按位异或 ~ 取反 << 左移 >> 右移
1.按位与运算 按位与运算符"&"是双目运算符。其功能是参与运算的两数各对应的二进位相与。只有对应的两个二进位均为1时,结果位才为1 ,否则为0。参与运算的数以补码方式出现。
如:9&5可写算式如下: 00001001 (9的二进制补码)&00000101 (5的二进制补码) 00000001 (1的二进制补码)可见9&5=1。按位与运算通常用来对某些位清0或保留某些位。
例如把a 的高八位清 0 , 保留低八位, 可作 a&255 运算 ( 255 的二进制数为0000000011111111)。
应用: a. 清零特定位 (mask中特定位置0,其它位为1,s=s&mask) b. 取某数中指定位 (mask中特定位置1,其它位为0,s=s&mask)
2.按位或运算 按位或运算符“|”是双目运算符。其功能是参与运算的两数各对应的二进位相或。只要对应的二个二进位有一个为1时,结果位就为1。参与运算的两个数均以补码出现。
如:9&5可写算式如下: 00001001|00000101 00001101 (十进制为13)可见9|5=13 应用:常用来将源操作数某些位置1,其它位不变。 (mask中特定位置1,其它位为0 s=s|mask)
3.按位异或运算 按位异或运算符“^”是双目运算符。其功能是参与运算的两数各对应的二进位相异或,当两对应的二进位相异时,结果为1。参与运算数仍以补码出现,
如9^5可写成算式如下: 00001001^00000101 00001100 (十进制为12)
应用: a. 使特定位的值取反 (mask中特定位置1,其它位为0 s=s^mask) b. 不引入第三变量,交换两个变量的值 (设 a=a1,b=b1) 目 标 操作 操作后状态 a=a1^b1 a=a^b a=a1^b1,b=b1 b=a1^b1^b1 b=a^b a=a1^b1,b=a1 a=b1^a1^a1 a=a^b a=b1,b=a1
4.求反运算 求反运算符~为单目运算符,具有右结合性。 其功能是对参与运算的数的各二进位按位求反。如~9的运算为: ~(0000000000001001)结果为:1111111111110110 5.左移运算 左移运算符“<<”是双目运算符。其功能把“<< ”左边的运算数的各二进位全部左移若干位,由“<<”右边的数指定移动的位数,高位丢弃,低位补0。 其值相当于乘以2。如: a<<4 指把a的各二进位向左移动4位。
如a=00000011(十进制3),左移4位后为00110000(十进制48)。 6.右移运算 右移运算符“>>”是双目运算符。其功能是把“>> ”左边的运算数的各二进位全部右移若干位,“>>”右边的数指定移动的位数。其值相当于除2。 如:设 a=15,a>>2 表示把000001111右移为00000011(十进制3)。注意:对于有符号数,在右移时,符号位将随同移动。当为正数时,最高位补0,而为负数时,符号位为1,最高位是补0或是补1 取决于编译系统的规定。Turbo C和很多系统规定为补1。
易出错的地方:
1. 如果对几个变量赋予同一个初值,应写成: int a=3,b=3,c=3; 表示a、b、c的初值都是3。不能写成: int a=b=c=3;
2.注意int ,float ,char 在不同版本中所占的字节数。如:int ,float,char在Visual C++ 6.0版本中所占的字节数分别为4,4,1 而在Turbo C 2.0
3.强制类型转换其一般形式为(类型名)(表达式)表达式应该用括号括起来。如 (int)(x+y)表示将x+y的值转换成整形如果写成(int)x+y则表示将x转换成整形,然后与y相加。
4.在强制类型转换时,得到一个所需类型的中间变量,原来变量的类型未发生改变。
5.溢出问题:如将一个double型数据赋給double变量时,截取其前面7位有效数字,存放到float变量的存储单元中时,等等
6.优先级的先后顺序问题,在不明确运算符的优先级顺序时,最好使用括号,以免出现不必要的错误。
7.使用scanf函数注意的问题: ①“格式控制”后面应当是变量地址,而不应是变量名。如 scanf(“%d”,a)是不对的 ,应改为scanf(“%d”,&a); ②scanf函数格式控制最后面不能有/n否则将没有结果输出如scanf(“%d/n”,&a);是不对的。 ③输入数据时不能规定精度, 如scanf(“%7.2f”),&a);是不合法的 ④如果在%后有一个“*”附加说明符,表示跳过它指定的列数。
8.数组下标越界。下标从0开始。数组名就是该数组首元素的地址。