《C语言深度刨析》整理

您所在的位置:网站首页 c语言深度刨析 《C语言深度刨析》整理

《C语言深度刨析》整理

2023-09-08 18:21| 来源: 网络整理| 查看: 265

 

  指针是c/c++ 精华,没有很好的掌握指针,基本是没有掌握c/c++,对c/c++ 也是一知半解,往往指针掌握不好,也不能很好的理解数组和内存管理

一、指针在系统同占用的空间

        在32位系统中,指针所占空间的大小为 4个字节,与指针指向的数据类型无关;

       比如   int  *p; char *p;  double *p; int **p;  已经执行构造类型的结构体的(比如指向  结构体,联合体等的指针)大小均为4个字节

       int *p;  表示内存中分配四个字节的内存空间,并将这块空间命名为p,并且这块内存p 中存入的数据都当做地址来看,大小是4个字节

二、int *p = NULL 和*p = NULL 区别

       (1) int *p = NULL;

          表示定义了一个指向int 类型的指针 p(p指向的内存用来存放int类型的数据),并将p 初始化为NULL,即p 指向内存为NULL地方(内存地址0x00000000);

       (2) int *p;    *p = NULL;

         表示定义了一个指向int类型的指针 p(p指向的内存用来存放int类型的数据),注意此处p 未被初始化,p可能指向内存中一个非法的内存地址,而这时对*p = NULL; 赋值

        可能是对非法的内存地址进行赋值;

        解析:区别:(1)表示对p赋值(定义p指向的内存地址)(2) p 指向未定义,p指向随机的一块内存地址,是对p指向的内存进行赋值

        注意:定义变量的时候,定义的同时一定要对变量进行初始化;

三、将数值存储到指定的内存地址

       现对内存为0x0012ff60 的内存进行赋值0x100(首先保证0x0012ff60这个内存地址是可以访问的)

      (1) int *p = (int *)0x0012ff60;

                   *p = 0x100;

       (2) *((int *)0x0012ff60) = 0x100;

四、数组内存布局

        int a[10] = {0};

        表示在内存中申请了一块内存 大小是 sizeof(int)*10,这块内存存放10个int 类型的数据,整块内存命名为a,这块内存没有名字,要访问内存中的各个元素只能通过数组

名a 加上下标或者偏移量的形式访问;

       (1) a 作为右值的时候表示数组首元素首地址; sizeof(a) = 40 (32位机下)

      (2)  &a 表示数组的首地址, &a + 1 将偏移 sizeof(int) * 10 个大小的空间

       (3)a 不能作为左值:a 的值不可改变(数组的访问要通过a加地址偏移或者下标形式访问)a 不能作为左值

               比如a++ 是错误的, a++ 等价于 a = a + 1;(编译错误)

               int *p = NULL;  p = a + 1;  (编译正确)

       (4)&a[0]和&a 区别

              虽然&a[0]和&a 值是相同的,但表示的意义不同:

            &a[0] 表示a[0]的地址(数组首元素首地址)

            &a 表示的是整个数组的首地址

五、指针和数组的关系

        指针和数组无任何关系

         (1)指针就是指针,指针变量在32 位系统下,永远占4 个byte,其值为某一个内存的地址。指针可以指向任何地方,但是不是任何地方都能通过这个指针变量访问到

         (2)数组就是数组,其大小与元素的类型和个数有关。定义数组时必须指定其元素的类型和个数。数组可以存任何类型的数据,但不能存函数

六、指针、数组访问

        看下面一道例题:

view plain print ? int main()    {        int a[5] = {1,2,3,4,5};        int *ptr = (int *)(&a+1);        printf("%d, %d", *(a+1), *(ptr-1));        return 0;    }   

 

解析:

       (1) &a + 1  表示自数组的首地址开始,偏移了&a +  5*sizeof(int) 大小的地址, 指向数组a[4]即 元素5 后面的元素;

                 (int *) (&a + 1)  然后强制类型转化为指向int 类型的指针,然后赋值给 ptr,ptr的偏移量n, 就转化为偏移 n*sizeof(int) 大小

                所以,*(ptr - 1) 值为 5

      (2) *(a+1) 表示数组首元素首地址偏移一个 sizeof(int) 的地址, 所以*(a+1) 值为2

七、代码在一个地方定义为指针,在别的地方也只能声明为指针;在一个的地方定义为数组,在别的地方也只能声明为数组

八、指针数组和数组指针

      (1)指针数组:首先它是一个数组,数组的元素都是指针,数组占多少个字节由数组本身决定。它是“储存指针的数组”的简称。 比如: int *p1[10];

      (2)数组指针:首先它是一个指针,它指向一个数组。在32 位系统下永远是占4 个字节,至于它指向的数组占多少字节,不知道。它是“指向数组的指针”的简称

                比如: int (*p2)[10];

九、指针数组经典例题

         在x86 系统下,其值为多少?

view plain print ? int  main()   {       int a[4]={1,2,3,4};       int *ptr1=(int *)(&a+1);       int *ptr2=(int *)((int)a+1);       printf("%x,%x",ptr1[-1],*ptr2);       return 0;    }  

        解析:

        (1) ptr1 的值为 4 (解析见前面)

         (2) 首先要确定系统是大端还是小端模式

               确定大端小端模式函数:  函数返回值为1:小端模式;函数返回值为0:大端模式

               

view plain print ? int  checkSystem()      {           union test           {               int i;               char ch;           }a;           a.i = 1;          return  ( a.ch == 1);       }  

          若checkSystem() 返回值 为1,表示系统为小端模式, *ptr2 值为 2000000

          若checkSystem()返回值 为0,表示系统为大端模式,  *ptr2 值为  100

十、二维数组

     1. 二维数组的地址

       char a[3][4];

      二维数组在内存中是以线性的形式存储的,char a[i][j] ;编译器总是将二维数组看成是一个一维数组,而一维数组的每一个元素又都是一个数组

     a[i][j]的首地址为&a[i]+j*sizof(char)。再把&a[i]的值用a 表示,得到a[i][j]元素的首地址为:a+ i*sizof(char)*4+ j*sizof(char)。同样,可以换算成以指针的形式表示:*(a+i)+j

      2. 二维数组的初始化

    

view plain print ? #include    int  main(int argc,char * argv[])   {        int a [3][2]={(0,1),(2,3),(4,5)};        int *p;        p=a [0];        printf("%d",p[0]);    }  

    解析: 结果是 1,而不是0  注意例题中初始化的时候,用到的是括号表达式而不是大括号

   在初始化二维数组的时候一定要注意,别不小心把应该用的花括号写成小括号了。

十一、二级指针

       char **p;      定义了一个二级指针变量p。p 是一个指针变量,在32 位系统下占4 个byte。它与一级指针不同的是,一级指针保存的是数据的地址,二级指针保存的是一级指针的地址。

     注: 多维数组和多级指针可以根据二维数组和二级指针依次类推

十二、数组参数和指针参数

     1. 不能向函数传递一个数组

          比如:       

view plain print ? #include "stdio.h"      void fun(char a[10])   {       ......   }      int main()   {       char b[10] = "abcdefg";       fun(b[10]);       return 0;   }  

解析:

     (1):b[10] 代表的是数组的一个元素,不能代表数组

     (2):b[10] 数组越界了

     (3):void fun(char a[10]); 实际是要传递的是一个char类型的指针;参数可以改写为

                    void fun(char a[]); 或者 void fun(char *a);

     (4):函数调用的时候可以改写成 fun(b);

    总结:C 语言中,当一维数组作为函数参数的时候,编译器总是把它解析成一个指向其首元素首地址的指针。

  2. 不能把指针本身传递给函数

    例一:  

view plain print ? #include "stdio.h"      void fun(char *p)   {       char c = *(p+3);       printf("%c\n", c);   }      int main()   {       char *p2 = "abcdefg";       fun(p2);          return 0;   }  

解析:

       (1). p2 是main 函数内的一个局部变量,它只在main 函数内部有效。(注意:main 函数内的变量不是全局变量,而是局部变量,只不过它的生命周期和全局

                   变量 一样长而已。全局变量一定是定义在函数外部的)

      (2). 对实参p2做一份拷贝并传递给被调用的函数。即对p2 做一份拷贝,假设其拷贝名为_p2。那传递到函数内部的就是_p2 而并非p2 本身。

     例二:

view plain print ? #include "stdio.h"   #include "stdlib.h"   #include "string.h"      void GetMemory(char * p, int num)   {       p = (char *)malloc(num * sizeof(char));   }   int main()   {       char *str = NULL;          GetMemory(str, 10);       strcpy(str, "hello");       free(str);//free 并没有起作用,内存泄漏       return 0;   }  

解析:

          通过编译可以看到,str的值仍未NULL。因为str 传到函数内部的是str的拷贝假设名字是_str, 当函数GetMemory(char *p, int num) 退出时,申请的内容

 同时释放掉并未传递给str(malloc 的内存的地址并没有赋给str,而是赋给了_str。而这个_str 是编译器自动分配和回收的),导致内存访问异常;

 例二:解决方法

        (1) 增加return,将申请的内存返回给str,同时要用str 进行接收

view plain print ? #include "stdio.h"   #include "stdlib.h"   #include "string.h"      char * GetMemory(char * p, int num)   {       p = (char *)malloc(num * sizeof(char));          return p;   }   int main()   {       char *str = NULL;          str = GetMemory(str, 10);       strcpy(str, "hello");       free(str);          return 0;   }  

    (2)使用二级指针

view plain print ? #include "stdio.h"   #include "stdlib.h"   #include "string.h"      void GetMemory(char ** p, int num)   {       *p = (char *)malloc(num * sizeof(char));      }   int main()   {       char *str = NULL;          GetMemory(&str, 10);       strcpy(str, "hello");       puts(str);       free(str);        return 0;   }  

解析:

        这种方法真正的将str传到函数GetMemory(char **p, int num) 中         (1)GetMemory(&str, 10); 这里传递的是 str的地址,函数内部申请空间赋值给 *p(也就是str),malloc 分配的内存地址是真正赋值给了str

 3. 二维数组参数和二维指针参数

      void fun(char a[3][4]);

      可以把a[3][4]理解为一个一维数组a[3],其每个元素都是一个含有4 个char 类型数据的数组。上面的规则,“C 语言中,当一维数组作为函数参数的时候,编译器总是把它解

析成一个指向其首元素首地址的指针。所以上面表达式可以改为:   void fun(char a[ ][4]); 或者 void fun(char (*a)[4]);

     C 语言中,当一维数组作为函数参数的时候,编译器总是把它解析成一个指向其首元素首地址的指针。这条规则并不是递归的,也就是说只有一维数组才是如

此,当数组超过一维时,将第一维改写为指向数组首元素首地址的指针之后,后面的维再也不可改写

十三、函数指针

        函数指针的形式之一: char * (*fun1)(char * p1,char * p2); (注意:此处的fun1 是指向函数的指针,而不是函数名,此函数是匿名的)

    1、函数指针的使用

        比如:

view plain print ? #include    #include       char * fun(char * p1, char * p2)   {       int i = 0;       i = strcmp(p1, p2);          if (0 == i)       {          return p1;       }       else       {          return p2;       }   }      int main()   {       char *str = NULL;       char * (*pf)(char * p1, char * p2);          pf = &fun;          str = (*pf)("aa", "bb");       puts(str);          return 0;   }  

解析:

使用指针的时候,需要通过钥匙(*)来取其指向的内存里面的值,函数指针使用也如此。通过用(*pf)取出存在这个地址上的函数,然后调用它。这里需要注意到

是,在Visual C++6.0 里,给函数指针赋值时,可以用&fun 或直接用函数名fun。因为函数名被编译之后其实就是一个地址,所以这里两种用法没有本质的差别。

  2. (*(void(*) ())0)() 的含义

     解析:

      (1)void(*)()    定义了一个函数指针

      (2)(void(*)()0)  将0强制转化为一个函数指针,0是个地址,这个匿名函数保存于起始地址为0的一块内存中

      (3)(*(void(*) ())0),这是取0 地址开始的一段内存里面的内容,其内容就是保存在首地址为0 的一段区域内的函数

      (4)(*(void(*) ())0)(),这是函数调用

  3. 函数指针数组

      char * (*pf[3])(char * p);

      解析:

      pf 是数组名,这个数组是指针数组,数组中的每个元素均为指针(指向函数的指针)

      例题:指针数组的使用

view plain print ? #include    #include    char * fun1(char * p)   {       printf("%s\n",p);       return p;   }      char * fun2(char * p)   {       printf("%s\n",p);       return p;   }      int main()   {       char * (*pf[3])(char * p);          pf[0] = fun1; // 可以直接用函数名        pf[1] = &fun2; // 可以用函数名加上取地址符           pf[0]("fun1");       pf[1]("fun2");          return 0;   }  

 



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3