说说对C语言指针的了解

说说对C语言指针的理解

我能写出来的暂时都还是很浅层次的内容,水平有限请谅解,期盼大神的指正。

 

指针困扰了无数学习编程的人,或许你的老师会告诉你,指针比较难学。

然而,不要被吓到,本宝宝就是当时被老师的话唬住所以学习指针那章的时候都没心情听课。(说得像讲别的内容时我听了似的,just kidding)

导致了学习链表的时候各种卧槽。

 

*************************** 基本介绍 **************************

 

程序中数据的使用往往是以变量的形式出现,每个变量都对应若干存储单元,变量的值存储在存储单元中,通过对变量的引用和赋值就可以使用或修改存储在存储单元中的数据。

                                                                                                                                                            ——《C语言程序设计与实践》

如果一个变量的地址存放在另一个变量中,则存放地址的变量叫指针变量。

指针就是地址,指针运算就是根据地址得到那个地址下存放的数据。

常用的是一级指针,二级指针一般比较少用。

 

*************************较为形象的理解 ************************

 

比如:让你去教学楼A214教室找一个人a,A214就是这个人所在的地址,A214就是一个一级指针,你有这个地址,你就能找到这个人。

   类似的,二级指针则是:A214教室里没有这个人,但是门上贴着另一个地址:A215,然后你来到A215,找到了这个人,那么A214就是一个二级指针

     因为你要经过两次寻找,才能找到这个人。

 

**************************** 进入正题 *************************

指针的声明及初始化:

 1  //第一种:声明并初始化
 2  
 3  int a = 3;
 4  int *p = a;  
 5 /* 定义指针变量 p 为指向整型数据 a 的指针,这一句写成 int *p = &a 同样是对的,一个意思,这里的 *号只是表示P是一个指针,并不是做指针运算。
但是在DEVC++环境下像第四行代码那样写(不写&符号)会有警告
*/ 6 7 //第二种:仅声明 8 9 float *p; //定义一个指向单精度型数据的指针变量 p 10 11 // 书面一些讲, 定义一个指针变量的一般形式是: 12 // <类型> *<变量标识符> 13 // 变量标识符,就像上面的 p 14 // 指针的英文是pointer

 

 

一段简单的代码

 1 #include <stdio.h>
 2 int main()
 3 {
 4     int x,y;
 5     int *px,*py,*p;
 6     px = &x;  // &是取地址符号
 7     py = &y;
 8     x = 21;
 9     y = *px;
10     //指针运算,即取出 px 这个地址(也就是 x 的地址)对应的值赋给 y
11 
12     p = &y;
13     printf("%d,%d,%d,%d,%d\n",*px,*py,x,y,*p);
14     return 0;
15 }
16 
17     /*  输出结果:分别是对 px 做指针运算得到的21,对 py 做指针运算得到的
18     21,x的值21,y的值21,对p做指针运算得到y的值21  */

 

注意事项:

1. 指针不能指向一个没有被定义的变量,这也是第一行代码存在的原因。

2. 指针不能指向具体的数值,例如:int *p = 3,这是错误的。

3. 分清楚当前指针是在做指针运算还是表明该指针指向某个变量。

 

第三条个人认为非常重要,不然会理解不了很多东西,不要一看到 * 号就觉得是在做指针运算(即把那个地址对应的值给取出来),因为有的时候只是表明那个变量的指针身份,表指向而已。

 

***************************** 指针与数组 ******************************

 

数组的名字就是它的首地址,这个首地址是不能被改变的,数组可以转换成指针。

1 int a[3];
2 for(int i = 0; i<3; i++)
3 {
4     *(a+i) = i;
5     //  *(a) = i; a++;  这样写是错误的,a是指针常量,常量不能被修改
6 }

 

***************************** 指针常量 *******************************

 

形如:int * const pa = a; 

仔细观察一下,*号在const前面,说明pa是常量。

1 int a = 1;
2 int b = 3;
3 int *const pa = &a;
4 a = b;   //  这句话也可以用*pa = b来代替
5 //  如果写 pa = &b 显然是不行的,因为pa这个指针是常量指针
6 printf("%d",*pa);
7 //  输出结果是3

说白了,不能写pa = xxxx,但是可以写*pa = xxxx

 

***************************** 常量指针 ****************************

 

形如:int const *pa = a;

这里int和const谁前谁后都是一样的

1 int a = 2;
2 int b = 3;
3 const int *pi = &a; //仔细观察可以看出 pi 是指向常量的指针
4 pi=&b;     //注意这里,pi可以在任意时候重新赋值一个新内存地址
5 b = 30;    //如果这里用*pi=30,是错误的
6 printf( “%d”, *pi ) ; 
7  // 输出是30

有人会疑惑,这明明是指向常量的指针,为什么指向的那个数据却从2变成了30?

好吧,是因为pi存的地址是可以变的,其实只是说不能写*pi = xxxx 而已,不能这样改变,但是可以用别的方式改变。

对比上面两端代码看看,应该就明白了。

 

************************ 为什么指针变量要分类型 **********************

 

有的人疑惑,如果初始化指针指向一个已经声明过的变量,是不是可以不用写指针类型?因为指针类型就是它指向的数据的类型啊?

 

1 int a = 3;
2 char ch = 1; //如果这里写'1'后面会输出49,因为是ASCII码,ASCII码中的字符1对应十进制49
3 
4 char *p = &ch; 
5 printf("%d",*p); 

请记住:指针的类型决定这个指针会读多少个字节的数据出来。(至于怎么读,从低往高读还是从高往低读,数据存储是从高到低还是从低到高,我还没有理解清楚)

想了解的朋友可以到这里看理解较深的大大写的数据变量在内存中的存储方式:http://blog.sina.com.cn/s/blog_abc091cc0101h0a3.html

                                                           堆区,栈区,静态存储区详解:http://my.oschina.net/lxrm/blog/513794

所以声明了类型会更安全。

p读取一个字节的数据,也就是ch的内容 :1

然而如果把第四行改成int *p = &ch,最后输出的就不一定是1了,这要看内存分配,这个指针会读取4个字节(32位平台下int型占4个字节)的数据,非常危险。

 

************************** 指针在函数中的运用 *************************

 

 请看下面一段代码

值传递:

 1 #include <stdio.h>
 2 
 3 
 4 void fun(int a1,int b1)
 5 {
 6     int temp = a1;
 7     a1 = b1;
 8     b1 = temp;
 9 }
10 
11 int main()
12 {
13     int a = 3;b = 4;
14     fun(a,b);
15     printf("%d %d",a,b);
16     return 0;
17 }
18 
19 // 输出结果:3 4
// a1 = 4,b1 = 3,a = 3,b = 4

观察这个函数,简洁明了啊,就是把两个参数交换。但是为什么后面的输出并没有把a,b的值交换?
是这样的:在给函数传递参数的时候,隐式地做了赋值,把调用函数那一句话括号里面的参数赋给了形参。

fun(a,b);    //a1 = a; b1 = b; 

进入函数以后,操作数是被赋值过后的a1和b1,所以只是把被a,b分别赋值后的a1,b1作交换,并不是a,b。

这是初次学习函数的同学们常常犯的错误。

 

看看下一段代码
地址传递:

 1 #include <stdio.h>
 2 
 3 void fun(int *pa,int *pb)
 4 {
 5     int temp = *pa;
 6     *pa = *pb;
 7     *pb = temp;
 8 }
 9 
10 int main()
11 {
12     int a=3,b = 4;
13     fun(&a,&b);
14     printf("%d %d",a,b);
15     return 0;
16 }
17 
18 输出结果:4 3

传给函数作为参数的是a,b的地址,pa = &a, pb = &b,从这两个地址下取出来的值*pa,*pb当然就是a,b的值了。

然后再交换,所以交换成功。

 

引用传递:

void fun(int &a,int &b)
{
    int temp = a;
    a = b;
    b = temp;
}

int main()
{
    int a = 3,b = 4;
    fun(a,b);
    printf("%d %d",a,b);
    return 0;
}
// 运行结果:4 3

// 这段代码在DEVC++环境下(codeblock也是这样,别的环境我没有试过,可能都会这样),如果选择建立.c而不是.cpp文件,使用引用传递会报错,因为引用传递严格来说不属于C语言的内容


这三段代码中的第二段可以视作是C语言的模拟引用传递

 

 *******************************指向函数的指针******************************

 

某一数据变量的内存地址可以存储在相应的指针变量中,函数的首地址也可以存储在某个函数指针变量里,这样就可以通过这个函数指针变量来调用所指向的函数。

函数指针变量和别的变量一样,也是需要声明后才能用的,相似于函数void fun(int)的声明,指向这个函数的指针要写成void (*pfun)(int)

于是:void fun(int);

         void (*pFun)(int);

这两行就完成了一个函数指针的声明。

 

 1 #include <stdio.h>
 2 
 3 void fun(int x)  // 如果此处只是声明,不写实现,x可以不写,只用写参数数据类型
 4 {
 5     printf("%d\n",x);
 6 }
 7 
 8 int main()
 9 {
10     void (*pFun)(int); //括号内也可以写int x
11     pFun = &fun;   // 令pFun函数指针指向fun函数
12     fun(10);    // 直接调用函数 
13     (*pFun)(20) ;   //  通过指针调用函数
14     return 0;
15 }
16 
17 // 运行结果:10
18             20

 

看到这里,希望觉得自己不懂指针的人会感觉指针并不是那么难懂,虽然我写的都是很浅层次的内容,水平有限请谅解。