白话编程--数据篇(2)指针

前言

        数据中最重要的部分,没有之一. 

        和数据结构紧密相连.建立符合逻辑结构的数据集合必备.

        指针的内容比较多,尽可能用场景说明,更多需要自己发掘.

引入

        计算机中所有数据都在内存中存放,他们都拥有自己的"地址",也就是指针.底层有一种机制,可以用访问地址来访问地址中的数据,这就是指针的意义所在.

        指针的意义是间接访问数据

指针写法

        数据类型 * 指针变量名=数据地址 

        说明:数据地址通常用"&+数据变量名"来表示,数组名自动表示为数组首元素地址

        当把某个数据的地址赋给指针,也称作指针指向了这个数据

        和前面声明某数据类型的变量做对比: "类型名 变量名=值",二者含义基本一致.

        举例
int a=10;
int *p=&a; /*整型指针p指向整型变量a*/

int b[]={10,20,30};
int *pa=b;  /*整型指针pa指向整型数组b*/

        指针可以指向单个数据,但不常用;                                               //前两行

        指针更多的用途是指向数据集合(包括数组,链表)内元素             //后两行

指针基本用法

        当使用指针指向了某个数据之后,例如int *pa=b,即可以用*pa来间接访问该地址内数据;同时指针支持加减法,*(pa+1)或者pa[1]表示偏移一个地址后数据的取用

        比如:已知某整型数组内有n个元素,打印出来

 void print_int(int *p,n){ //形参里的"int*"表示这是一个整型指针类型
    for(;p

 调用print_int(b,3)或者print_int(pa,3),将上面的数组b[]和元素数量3传入,得到打印结果.

        ---因为C语言是按值传递的,所以传入的是变量或者常量都可以,为形参传入变量时会查找变量   对应的常量(值)

注意:

      1>  因为C语言的独特设计,在声明指针类型,以及函数形参类型时,用到的" * "表示这是一种指针类型,其他地方的"*+指针变量名"表示指针指向地址里的值.这是初学者比较容易迷惑的地方,当然我们也没法去质疑语言的设计者为什么要这样设计,我们只需要记住这个规则即可.

       2> 指针的边界需要程序员自己去掌握.

        按照上面定义的指针pa指向数组b[],以下几种情况成立:

        *pa==10;  *(pa+1)==20; *(pa+2)==30;

        如果把上述式子都看成条件表达式,他们的值都为true.     if( *pa==10)相当于if(true)

        但是:*(pa+3)等于多少? 这个是未定义的,结果是不确定的.所以指针不允许这样使用,称为指针越界访问内存.

        同样道理,在调用print_int()函数时,事先知道数组里有3个元素,所以传入3没有问题,但是如果传入其他数字,比如4,则会出现意想不到的结果,这也是被禁止的.

指针的使用禁忌(注意事项)

        1>指针必须指明其数据类型,即属于什么类型的指针.

         int *p=a;//假设存在a[]这样的数组

         指明了指针是属于整型,然后可以间接访问整型数组里的元素. 这一点很好理解,如果脱离了数据元素类型,采用了 *p=&a;  来定义估计没人看得懂.

当然C语言里有void *这样的指针,但是建议不要用,因为这可能超出了程序员对数据的控制范围

        2>指针必须指向已存在的数据.

        书上有一句话:指针在使用前必须初始化.可能有点难以理解,换成这个说法可能会好一些.

        前面讲到指针的意义在于间接访问数据.如果不把指针指向已存在的数据(已开辟的内存空间),指针不仅没有意义,而且是错误的.

        3>指针不能指向具体的内存地址(非绝对)

        指针表示地址,他的值就是指针指向数据在内存中的地址值.类似于0x0036FE96(假设32位机)这样的值.在C中这样的写法是禁止的:

        int *p=(int *)0x0036FE96;   //错误写法!禁止!

        这表示程序员想直接访问内存地址.但内存地址使用是要经过操作系统允许的.芯片把这个权限交给了操作系统.操作系统有自己的区间划分.即使程序员想要查询自己定义的变量放在哪个地址上,返回的也是虚拟内存地址,而非实际物理地址.

            int *p=a;//假设存在a[]这样的数组,允许!

        话说回来,如果要操作硬件,需要将硬件寄存器映射到某一片内存空间上,这时候可以在允许的内存空间上操作.这不在软件程序员讨论范畴.

        一般情况下,不能直接操作内存地址.特殊情况下可以,所以说不是绝对的

        4>在函数内部建立指针的临时变量时,小心内存泄漏

        举例:写一个函数,建立一个整型数组,整数个数由自定义输入,数组内所有元素等于0;

 void createArray(int n){

   
    int* p = (int*)malloc(sizeof(int) * n);

    for(int i=0;i

        乍一看这段代码没有问题.但是因为是在函数中临时申请了内存空间,在函数调用完毕后,没有释放相应的内存空间(free),所以造成了内存空间被占用,而且不被发现.这叫做内存空间泄露.

        但我们可以解决这个问题:既然申请了内存空间,得到了指针.那么返回的指针由定义指针接收,则保持对这部分数据的访问权限.

 int * createArray(int n){

   
    int* p = (int*)malloc(sizeof(int) * n);

    for(int i=0;i

        5>指针可以指空(NULL) 

        这点和2>并不矛盾,指针可以指空,常用于链表末尾.

指针的多样性 

        从前面内容已经看出来,指针和数组(当然也包括链表),指针和数据集合是紧密联系在一起的.一个必须有的概念应该形成:只要有数据集合,必然有指针去读取或修改. 简单梳理一下逻辑:数据集合必然需要遍历(访问其中的单个元素),遍历必然要用到指针.

        指针和数据集合存在以下关系:

        1>可以定义多个指针指向同一个地址.

        例如:

        int a[]={10,20,30};

        int *p=a;        //定义指针p指向数组a

        int *q=a;         //定义指针q指向数组a

         2>可以定义集合内某具体位置的指针.

        int *first=&a[0];//定义指向数组首元素

        int *last=&a[2];//定义指向数组末元素

你可能感兴趣的:(c++)