PoEdu - C提高班 【Po学校】-Lesson08 -指针专题(一):指针&变量&数组- 课堂笔记

 

1-->  变量  (占用指定长度的内存空间)

 

 1.0  变量的本质是什么?为什么要用“变量”?看两面模拟变量出现的演变过程:

每一个[]中表示一个内存地址;

 

 回头看变量的表示:

 如上图所示,“变量”出现就有了意义。

 变量名 = 地址 + 长度的值


 

 

2--> 指针  (代表一个起始地址)

 

 

2.0  指针是一个变量

2.1  拥有所有变量的属性

2.2  指针变量 =  指向变量的地址

2.3  指针类型决定使用时*(解)的长度

2.4  指针它代表一个内存地址,不代表长度以及解析方式

2.5  示例

#include <stdio.h>

int main()
{
   int *pn = NULL;  //pn变量--> 空间地址 + 长度
   printf("指针变量的地址:%p, 指针变量的长度:%u, 指针变量的值:%p \n",&pn, sizeof(pn), pn);
   
   return 0;
}

运行结果:指针变量的地址:005EFDD4, 指针变量的长度:4, 指针变量的值:00000000

2.6  与变量作比较

修改代码:

#include <stdio.h>

int main()
{
    int pn = NULL;  //pn变量--> 空间地址 + 长度  这里的NULL可以改为0,也可以理解为0。
    printf("int变量的地址:%p, int变量的长度:%u, int变量的值:%p \n", &pn, sizeof(pn), pn);

    return 0;
}

对比指针:

 验证:指针也是一个变量

2.7  指针和变量的区别:值所表示的意义不同

2.8  指针变量存放的是一个内存地址,而int类型的变量存放的只是一个整数。所以相对普通的变量,指针变量就有了解引用:*pn =10 

2.9  指针的解引用流程图:

2.9.0  代码:

#include <stdio.h>

int main()
{
    int n = 100;
    int *pN = &n;  

    printf("n:%d, &n:%p, pN:%p, &pN:%p \n", n, &n, pN, &pN);
    *pN = 10;
    printf("n:%d, &n:%p, pN:%p, &pN:%p \n", n, &n, pN, &pN);
    pN = 10;
    printf("n:%d, &n:%p, pN:%p, &pN:%p \n", n, &n, pN, &pN);
    return 0;
}

2.9.1  *pN = 10;    左边*pN分成3个动作:1找到pN的内存地址;2读取这段空间的值,发现它是一个内存地址(指针);3用这个内存地址(指针)所指向的位置,找到另外一段内存空间。

 

 2.9.2  第1步  找到pN地址  

2.9.3  第2步  找到里面的指针(内存地址)              

  

      

2.9.4   第3步  找到指针指向的空间              

     

2.9.5  第4步  用10把里面的值覆盖

 

 2.10  指针的类型  (解引用时需要)

 2.10.0  看代码:

#include <stdio.h>

int main()
{
    int n = 100;
    int *pN = &n;  
    char c = 'a';
    pN = &c;
    printf("c:%d, &c:%p, pN:%p, &pN:%p, *pN:%d \n", c, &c, pN, &pN,*pN);    
    return 0;
}

2.10.1  看解引用 *pN的流程图:

 

 

2.10.1.0  第一步  找到pN的空间

 

 

2.10.1.1  第二步  找到pN里面的值:指针(内存地址)

 

2.10.1.2  第三步  找到指针指向的空间地址

 

2.10.1.3  第四步  读取空间里面的值,读取多长呢?看指针的类型

 

2.10.1.4  最终*pN和结果不是我们想要的‘a’或者97。

2.10.1.5  这种未定义的行为一定要避免,知道第一个它是97,后面3个是什么谁也不知道,就会导致程序里面出现各种各样的错误。

2.10.1.6  为什么说指针代表的只是简单的地址,但是我们在定义指针变量的时候,还是要给出一个类型呢?如上流程图所展示的意义,当解引用的时候,我们需要知道要读取多长的空间。

 


 

3--> 变量与指针

 3.0  什么是变量?变量就是存储空间的别名。

3.1  为什么要使用变量?为了方便访问存储空间。

3.2  除了用变量名访问存储空间,还有指针可以访问对应的存储空间。

3.3  指针最大的作用是:使用指针,对存储空间进行访问。

3.4  访问存储空间,变量名访问的方式,比之指针访问方式更加安全。什么时候用变量名,什么时候用指针?只有在变量名无法使用的场合,在使用变量名无法达到需求的时候,就可以使用指针。

3.5  在哪些场合下,变量名访问无法达到需求呢?  1  局部变量,传递参数时。   2 动态分配内存时。

3.6  指针也是一个变量,指针变量的名相对普通变量,也可以当作是指针的变量名。它同样是存储空间的别名。

3.7  指针变量也会有指针(内存地址),指针变量最大的作用是保存一根指针(内存地址)。保存内存地址的变量,我们叫做“指针”。

3.8  是凡使用了“指针”,对存储空间的访问就一定是间接访问,因为“指针”对应的空间里面存储的是一根指针(内存地址);

3.9  从汇编来看变量名访问存储空间,以及用指针来间接访问存储空间,他们之间的效率:

3.9.0  代码:

#include <stdio.h>

int main()
{
    int n = 10;        //直接访问
    int *pn = &n;      //间接访问

    n = 100;
    *pn = 1000;    
    return 0;
}
#include <stdio.h>

int main()
{
01321680  push        ebp  
01321681  mov         ebp,esp  
01321683  sub         esp,0DCh  
01321689  push        ebx  
0132168A  push        esi  
0132168B  push        edi  
0132168C  lea         edi,[ebp-0DCh]  
01321692  mov         ecx,37h  
01321697  mov         eax,0CCCCCCCCh  
0132169C  rep stos    dword ptr es:[edi]  
0132169E  mov         eax,dword ptr [__security_cookie (01328004h)]  
013216A3  xor         eax,ebp  
013216A5  mov         dword ptr [ebp-4],eax  
    int n = 10;        //直接访问
013216A8  mov         dword ptr [n],0Ah  
    int *pn = &n;    //间接访问
013216AF  lea         eax,[n]  //lea命令  传递有效的地址。
013216B2  mov         dword ptr [pn],eax  

    n = 100;
013216B5  mov         dword ptr [n],64h    //直接一步到位
    *pn = 1000;
013216BC  mov         eax,dword ptr [pn]    //dword 双字 就是四个字节 //ptr  pointer缩写 即指针
013216BF  mov         dword ptr [eax],3E8h    //3E8的16进制是1000
 //比如mov eax, dword ptr [12345678] 把内存地址12345678中的双字型(32位)数据赋给eax   
    return 0;
013216C5  xor         eax,eax  
}

3.9.1  以上代码可以看出,使用变量和使用指针赋值的区别:1  使用变量赋值,一步到位。   2   使用指针赋值,先要有一步“寻址”的步骤,需要两步

3.9.2  此时变量直接赋值效率要高,另外,指针也是要占用空间的。所以此例中,使用指针赋值是没有使用变量名直接赋值效率高的。

3.9.3  只有当一个函数的参数传递,函数运行中要改变变量的值,此时才是指针的合适用处。代码:

#include <stdio.h>

void foo(int *n)
{
    *n = 100;
}

int main()
{
    int n = 10;        //直接访问
    int *pn = &n;    //间接访问

    n = 100;
    *pn = 1000;
    
    return 0;
}

  上面的foo函数,需要改变n的值,此时运用指针才是恰当的。

3.9.4  指针与const结合的助记口诀:左数右指


 

4-->  数组

 4.0  数组与指针的关系

#include <stdio.h>                                 

int main()
{
    int array[10] = { 1, 2, 3, 4, 5 };
    int *parray = array;
    printf("%d \r\n",*parray);
    printf("%d \r\n", *parray+1);
    printf("%d \r\n", *parray+2);
    printf("%d \r\n", *parray+3);
        
    return 0;
}

  用指针+1可以遍历数组成员

运行:

 

4.1  大部分时候,数组名可以认为是一个常量指针,但在两种情况下另外:1 在作sizeof运算时,它就不等于常量指针;2  在作&运算时,数组名就不能认为是常量指针。

 

4.2  我们对一个数组名作&(取址)运算时,会得出什么呢?

4.3  分析数组名:数组名就是数组首个元素地址的别名。数组名作&(取址)运算得出的地址还是这个地址,此时“&数组名”就包含了两层含义:1  此地址为数组的地址; 2  此地址为首个元素的地址。这里可以理解为一个套装的模块(数组)里面有很多小的元素块,首个元素块有一个唯一的标记(首地址),这个标记是此首元素的标记(地址),也是整个套装模块的标记(地址)。当我不加&(取址),直接用数组名运算+,或者运算减,此时是元素指针的运算。当我加上&(取址),那么“&数组名”就是数组指针的运算;差之毫厘,缪以千里。

4.4  看代码 ,array  与  &array 的不同。

#include <stdio.h>                                 

int main()
{
    int array[10] = { 1, 2, 3, 4, 5 };
    int *parray = array;
    int paddrarrray = &array;
   printf("%p \t%p \r\n", parray+1, paddrarrray+1);
    printf("%p \t%p \r\n",array +1, &array+1);        
    return 0;
}

   上下两个printf函数中,两者不同在于,上面的是int类型的指针参与运算,下面的&array是数组地址参与运算。前者计算公式: array + sizeof(*array),后者计算公式:*array + sizeof(*&array);  此例parray+1, paddrarrray+1始终相同,在于它们都是一个int*类型的指针,所以始终相同。

4.5   看示例,理解偏移量的不同:当元素大小为1byte时,偏移+1byte,而当数组大小为100时,偏移+100byte

4.6  看二维数组:

 

 

5--> 数组与指针的区别

 5.0.0  先看个示例:把一段指针的定义写入另外一个.c文件,在main.c中以数组形式引用,会发生什么样子的状况呢?

char *pstr = "I Love Mark!~";    //指针写在other.c中

 5.0.1  main.c中以数组方式欺骗编译器。

#include <stdio.h>                                 
extern char pstr[];  //以数组方式  欺骗编译器
int main()
{
    printf("%s",pstr);
    return 0;
}

5.0.2  运行结果:4k  不是我们想要的结果(I Love Mark!~)。

 5.0.3  指针有寻址动作,间接访问一根指针(内存地址)指向的空间,数组是直接访问,没有寻址动作,原始数据是什么,就反馈什么,没有其它的动作。

 

5.0.4  把指针看作数组后,它就失去了指针指向、寻址的意义,直接访问里面的数据。

5.0.5  如上示例,怎么把数组的直接访问,改写成指针的间接访问,达到目的呢?  第1步:要把pstr变成一个指针变量,(unsigned int*)pstr;   第2步: 把里面的数据取出,解引用;第3步:把解引用出来的数据强制转换成一根指向常量区的char*指针。

 

#include <stdio.h>                                 
extern char pstr[];
int main()
{        
    printf("%s",(char*)(*(unsigned int*)pstr));
    return 0;
}

5.0.6  指针在内存中是一个unsigned int 的类型,所以要先把pstr[]转换成unsigned int*,得以正确的格式处理里面的地址数据 。

 

5.1.0  指针的运算: 1 有加运算 +1 ; 2 有减运算  -1 ; 3 有“指针”与“指针”之间的“减”; 4  有“指针”与“指针”之间的等于等于 == ;5  p1 !=p2  指针之间的“非等” 。

5.1.1  指针的运算公式:1 p + sizeof(*p)  2 p - sizeof(*p)  3 (sizeof(p1) - sizeof(p2))/sizeof(*p), "指针"之间的“偏移量”  4 p1 == p2; “指针”是否相同  5  p1 !=p2  指针之间不相同  

5.1.2  利用指针之间的“偏移量”(指针之间的相减),能够控制指针之间的范围区间:

#include <stdio.h>                                 

int main()
{    
    int array[10] = { 1,2,3,4,5 };
    int *parray = array + 3;
    int *pother = array + 7;
    for (; pother - parray >=0; parray++)
    {
        printf("%d \n",*parray);
    }    
    return 0;
}

运行

 

 5.1.3  堆上的指针运算,是未定义的结果,是无意义的,就相当于对“野指针”进行操作。

5.1.4  只有连续内存空间的指针运算才是有意义的。

5.1.5  看示例:地址之间的比较,同样要在连续的空间才是有意义的。

#include <stdio.h>                                 

int main()
{    
    int array[10] = { 1,2,3,4,5 };
    int *parray = array + 3;
    int *pother = array + 7;
    while (parray != pother)
    {
        printf("%d \n",*parray);
        parray++;
    }    
    return 0;
}

运行:

 5.2  使用指针访问数组元素,与之使用下标访问数组元素,两者效率比较,指针访问的效率高于下标访问效率。

5.2.0  但现在的编译器已经做了很多的优化,在Release下面,已经没有区别了。

 

 

 


 

6--> 数组作为函数的参数传递

 6.0.0  数组当作为函数参数进行传递时,会降级降维,变成指针。

6.0.1  传递参数时降级,会很是彻底:在函数外部作为数组时,数组名是一个常量的指针,数组名不可以被更改。而作为函数的参数传递降级,函数里面指针是可以被修改的。

6.0.2  作为参数传递是创建一个副本,是一个copy,在函数里面,它完全成了另外一个普通的指针数据。

6.0.3  如果要函数改变外部数组元素,需要传递指针的指针。

#include <stdio.h>                                 
int foo(int **array)
{
    int n = 10;
    *array = &n;
    return 0;
}
int main()
{    
    int array[10] = { 1,2,3,4,5 };
    printf("%p \r\n",array);
    foo(array);
    printf("%p\r\n",array);
    for (int i = 0; i<10; i++)
    {
        printf("%d\r\n", array[i]);
    }
    return 0;
}

运行

 

posted on 2017-02-13 23:15  zzdoit  阅读(322)  评论(0)    收藏  举报

导航