zoukankan      html  css  js  c++  java
  • 【C_Language】---最全面的C指针总结,初级程序员必备

         好久没写博客了,重新学习C语言了的基础课程,发现很多东西都忘记的差不多了,闲来无事,总结一下关于指针的知识,希望能帮到像我一样的菜鸟们:

    指针,众所周知是C语言的精华所在,不懂指针的话,你就不要说你学过C语言,因为实在是太丢脸了。

    很多人,(当然,也包括我在刚学习指针的时候,被灌输的思想就是指针就是地址)然而,这仅仅是个部分正确的说法。指针与地址有着千丝万缕的联系,然而它又不完全同于地址,具体是什么,且听我慢慢道来。

    一、指针与地址的关系

      什么是地址?比如,你的内存有1K的存储空间,那么按字节给你的内存进行编号,你的内存将被划分称为1024个空间,编号0-1023,这就是你的内存地址。

      什么是指针:首先它是一种数据类型,你可以这样认识它:指针就是一个变量,这个变量的值就是一个地址,请注意,该变量的值是一个地址。   请看下面例子。     

    1 int a = 3;
    2 int *p= &a;
    3 printf("a的地址是:%d",&a);printf("a的值是:%d",a);
    4 printf("指针的地址是:%d",p);printf("指针所指空间的值:%d",*p);
    0x00 a = 3
    0x01  
    0x02 p = &a=0x00
    0x03  

    结合代码和示意图,定义了一个变量a,变量a的地址是0x00,而0x00地址中的数据是3,即a = 3,&a = 0x00;同理定义了一个指针变量p,p的地址是0x02,而0x02地址中的数据是p的值,既p = 0x00,&p = 0x02,也就是p的值是a的地址,那么*p,就是将0x00地址中的数据取出来,则*p=3;则以上代码的输出结果:  

                       a的结果是:0x00;      a的值是3;

                       指针的地址是:0x02;  指针所指空间的值是:3

    至此,你应该能理解指针和地址的关系了吧。

    二、指针的使用

           1.定义

                  定义方式:类型说明符*指针变量名;

                  类型说明符:int,char……用来表示指针中所存放的地址中的数据类型。

           2.初始化:

                         int a = 3;  int *p = &a;

                         int str[3] = {0};  int *p = str;

      3.赋值:

      4.空指针

                  用NULL初始化的指针   int *p = NULL;

                  指向0地址单元的指针。指针定义时若没有初始化,就需要赋空。

      5.野指针

        可以理解为,指向系统未知单元的指针,修改该指针会导致系统崩溃,编程要坚决避免也指针的出现。

     三、指针的大小

      无论你定义的是什么类型的指针,包括char、int、float等等,你的指针的大小都是4个字节(前提是在32位机器上的编译器)。

    四、参数传递

      在此要介绍的一个概念就是传值和传址。

      首先看一个传值的例子:

     1 int fun(int n)
     2 {
     3     n = 3;
     4 }
     5 
     6 int main(void)
     7 {
     8     int a = 5;
     9     fun(a);
    10     printf("a=%d",a);
    11 } 

       该代码的运行结果是 a = 5,而不是多数人理解的a = 3;原因就在于,你通过形参传递到函数fun()里面的仅仅是一个数值,而原本a地址中的数据并没有发生改变,所以你在调用函数fun之后打印出来的仍然是5,如果想要打印出来的数是3,那么你就需要使用到传址,代码做如下修改:

     1 int fun(int *n)
     2 {
     3     *n = 3;
     4 }
     5 
     6 int main(void)
     7 {
     8     int a = 5;
     9     fun(&a);
    10     printf("a=%d",a);
    11 } 

    这个时候打印出来的就是 a = 3;

      在使用指针做函数参数的时候,一定要注意的是传值还是传址。

    四、指针与数组

      指针与数组的难点无非就两点概念的掌握,一是指针数组,二是数组指针。

      指针数组的本质是一个数组,只是数组内的元素都是指针,每个指针都可以只想一个变量的地址,

     1 int main(void)
     2 {
     3     int i = 0;
     4     int *a[3];
     5     int b = 0,c = 1,d = 2;
     6     a[0] = &b;
     7     a[1] = &c;
     8     a[2] = &d;
     9     for(i=0;i<3;i++)
    10     {
    11         printf("%d ",*a[i]);
    12     }
    13 } 

      该程序的输出结果是: 0 1 2 

    而数组指针的本质仍旧是一个指针,它能指向一个数组, 

     1 int main(void)
     2 {
     3     int i = 0;
     4     int (*a)[2];
     5     int b[2][2] = {1,2,3,4};
     6     a = b;
     7     for(i=0;i<2;i++)
     8         printf("%d ",*(a[0]+i));    
     9     for(i=0;i<2;i++)
    10         printf("%d ",*(a[1]+i));
    11     printf("
    ");
    12 } 

      该程序的输出结果是:1 2 3 4

      对于数组指针你可以这样理解, 对于int(*s)[4]这样的一个数组指针,首先,这样的指针是指向一个int [4]这样的类型的数组,然后,单独的来看(*s),你把他当成是一个s[]这样的数组,那么s[0]就是第一个指针,这个指针指向一个int [4]类型的数组,s[1]就是第二个指针指向另外一个int [4]型的数组。

      假设定义了两个数组a[4],b[4],那么你可以用s[0] = &a[0];s[1] = &b[0]这样的方式来定义两个指针,分别指向a,b两个数组。想要取出所指向的a数组四个元素的话,你可采用数组表示发:s[0][0],s[0][1]……,若想取出数组b的四个元素,就是s[1][0],s[1][1]……,至此,相信你应该能够理解什么是数组指针了吧。

    五、指针与字符串

      学习C语言的都应该知道,我们通常是采用数组的方式来定义一个字符串,同时,C语言允许直接使用指针的方式来定义一个字符串,并且该字符串存储在内存中的文字常量区,生存周期直到整个程序运行结束。举个简单的例子。

     1 # include "stdio.h"
     2 # include "string.h" 
     3 
     4 char * fun1(void)
     5 {
     6     char *s = "szhb";
     7     return s;
     8 }
     9 
    10 char * fun2(void)
    11 {
    12     static char s[] = "szhb";
    13     return s;
    14 }
    15 
    16 char * fun3(void)
    17 {
    18     char m[] = {"szhb"};
    19     return m;
    20 }
    21 
    23 int main(void)
    24 {
    25     char *o,*p,*q;
    26     o = fun1();
    27     p = fun2();
    28     q = fun3();
    29     printf("%s
    ",o);
    30     printf("%s
    ",p);
    31     printf("%s
    ",q);
    32     return 0; 
    34 } 

      以上代码在VC6.0编译器下运行的结果是这样的。

      

      也许你会奇怪,fun1()函数为啥没错误,这就要注意上面我所说,C语言允许采用指针的方式来定义一个字符串,该字符串的存放位置以及生存周期,决定了fun2()函数是可以正常运行的,而fun3()错误之处就在于,你在函数体内定义了一个函数了数组,该数组是存储在栈空间上的,当函数运行结束的时候,栈空间被释放,此时你返回的地址,将是一片没有任何意义的地址,如果想用。

    五、指针与函数

      与数组类似,指针与函数的关于也在于两个概念的理解,指针函数和函数指针。

      返回类型为int型的无参数指针函数的定义 :*fun(void)   它的涵义是,该函数有一个返回值,返回值是一个指针,该指针指向一个整型变量的地址。

      看以下例子:

     1 int * fun(void)
     2 {
     3     static int a = 3;
     4     printf("%d
    ",&a);
     5     return &a;
     6 }
     7 int main(void)
     8 {
     9     int *p;
    10     p = fun();
    11     printf("%d
    ",p);
    12     return 0;
    13 } 

      该函数的运行结果如下:

      

      从结果可以看出,两次打印出的值是一样的。

      int型无参数的函数指针定义:int (*p)(void),本质是一个指针,指向一个函数的入口地址。

      看一下例子:

     1 int fun(void)
     2 {
     3     static int a = 3;
     4     printf("%d
    ",a);
     5     return a;
     6 }
     7 int main(void)
     8 {
     9     int (*p)(void);
    10     p = fun;
    11     p();
    12     return 0;
    13 } 

      运行结果如下:

      

      由结果可以看出,p指向的是函数fun(),的入口地址,执行该地址的函数p();结果同直接执行fun()函数是一样的。

    六、多级指针

      该部分也是重点掌握一个涵义吧,通常一级指针中存放的是变量的地址,那么二级指针中存放的是一个一级指针的地址,通过该一级指针的地址,再去访问变量的地址,就能访问该变量的具体数值。

      例如,

     1 int main(void)
     2 {
     3     int **p;
     4     int *s;
     5     int a = 3;
     6     s = &a;
     7     p = &s;
     8     printf("%d
    ",a);
     9     printf("%d
    ",*s);
    10     printf("%d
    ",**p);
    11     return 0;
    12 } 

      该部分代码打出的结果是3个3,p中存放的是指针s的地址,那么*p将会得到s中存放的a的地址,那么*(*p),将a中的地址中的数据再次取出来,就会得到一个3了。

      好了,指针部分的知识,就总结这么多了,以后再继续补充个吧,总结的也很一般,一方面为了记录自己的学习之路,另一方面也是希望能帮到广大的博友们。

    //*********************************实际中你可能会弄混的指针问题**********************************//

    1.

     1 void fun1(int p[])
     2 {
     3     int i = 0;
     4     for(i=0;i< sizeof(p)/sizeof(p[0]);i++)
     5     {
     6         printf("%d ",p[i]);
     7     }    
     8 }
     9 int main(void)
    10 {
    11     int str[4] = {1,2,3,4};
    12     fun1(str);    
    13 } 

      你能知道,最终的运算结果将会打印出数组中的几个数据吗?

      最终的结果是,在64位机器上运行时2个数据(1,2),在32位机器上是1个数据(1),为什么呢?虽然我们都知道,对于一个数组str[],是可以采用sizeof(str),来计算该数组的内存大小的,但是当你采用数组作为形参的时候,数组自动退化为一个指针,那么程序就会把形参p当成一个指针,你对一个指针进行sizeof()运算,那么不就是在计算指针的大小吗?64位机器上指针大小是8个字节,32位是4个字节,那么答案也就不言而喻了。

    2.指针在二维数组中的应用。

    情况一

    1 int main(void)
    2 {
    3     int s[4] = {1,2,3,4};
    4     int *p1 = (int *)(&s+1);
    5     int *p2 = (int *)&s+1;
    6     printf("%d %d %d
    ",*(s+1),*(p1-1),*(p2-1));
    7 } 

      该段代码的输出结果是 2,4,1,有没有想明白?

      *(s+1)就是s[1];   *(p1-1)就是s[3];  *(p2-1)就是s[0];
      对于p1来说,(&s+1)系统认为是在整个数组s[4]的后面地址加1,那么p1-1就是s[3]的地址。  
      对于p2来说,&s+1不是首地址+1,系统会默认加一个a数组的偏移,是偏移了一个数组的大小,那么p2-1就是s[0]的地址;
      原因:&s是数组指针,其类型是  int (s)[5];
    情况二
     1 int main(void) 
     2 {
     3     int s[2][2] = {1,2,3,4};
     4     int (*p)[2] = s; 
     5     printf("%d  ",p+0);
     6     printf("%d
    ",p+1);
     7     
     8     printf("%d  ",*(p+0));
     9     printf("%d
    ",*(p+1));
    10     return 0;
    11 }

      这是定义了一个数组指针指向一个二维数组的首地址,但是对于不同的打印方法,打印出来是一样的地址,原因p是一个只想 int [2]的指针,在程序中p指向二维数组s的收地址,p+1是加了一个数组的长度所以p+1应该是s[1]的首地址,假若,p是一个只想int [3] 的指针,那么p+1就应该加12个字节的地址。而打印出的结果相同,也是有点碰巧的意味了。


    3.最近在学习的时候,又碰到了一个有趣的关于较为复杂的传址和传值的问题。
      
     1 void fun5(char **p,int num)
     2 {
     3     *p = (char *)malloc(num);
     4 }
     5 
     6 int main(void) 
     7 {
     8     char *str = NULL;
     9     fun5(&str,100);
    10     str[1] = 1;
    11     printf("%d
    ",str[1]);
    12     free(str);
    13     return 0;
    14 }

      这是正确的代码,能够输出正确的结果1,跟下面的代码对比一下,值得你推敲一番。

      

     1 void fun5(char *p,int num)
     2 {
     3     p = (char *)malloc(num);
     4 }
     5 
     6 int main(void) 
     7 {
     8     char *str = NULL;
     9     fun5(str,100);
    10     str[1] = 1;
    11     printf("%d
    ",str[1]);
    12     free(str);
    13     return 0;
    14 }

      这段代码在编译上没有任何问题,但是运行时,整个程序就崩溃了,这是为何呢?

      这里再让我们细细的去考虑一下关于参数的传值和传址吧,在正确的代码中,形参定义的是一个指针的指针,一级*p 是一个地址,主函数我们将指针str的地址传入fun1()函数内,那么函数内的操作相当于改变了str的值,也就是str此时是我们申请的那片空间的地址,但是如果采用,错误的那种写法,形参传入的是一个值,函数内的操作,并没有改变str的值,也就是说,此时主函数中的str仍旧是一个指向NULL的指针,我测试了一下,结果却是如此。



                  

      

  • 相关阅读:
    第13章 TCP/IP和网络编程
    实验二测试
    实验四 Web服务器1socket编程
    thread同步测试
    团队作业(五):冲刺总结——第四天
    111
    递归和数学归纳法
    Nodejs中cluster模块的多进程共享数据问题
    JavaScript写类方式(一)——工厂方式
    JavaScript中的shift()和pop()函数
  • 原文地址:https://www.cnblogs.com/szhb-5251/p/5674222.html
Copyright © 2011-2022 走看看