zoukankan      html  css  js  c++  java
  • C语言-指针

    1、什么是指针(Point)?

    内存中数据字节的地址(Address)编号。计算机中所有的数据都必须放在内存中,不同类型的数据占用的字节数不一样,例如 int 占用 4 个字节,char 占用 1 个字节。

    数据和代码都以二进制的形式存储在内存中,计算机无法从格式上区分某块内存到底存储的是数据还是代码。当程序被加载到内存后,操作系统会给不同的内存块指定不同的权限,拥有读取和执行权限的内存块就是代码,而拥有读取和写入权限(也可能只有读取权限)的内存块就是数据。

    变量名和函数名为我们提供了方便,让我们在编写代码的过程中可以使用易于阅读和理解的英文字符串,不用直接面对二进制地址。需要注意的是,虽然变量名、函数名、字符串名和数组名在本质上是一样的,它们都是地址的助记符,但在编写代码的过程中,我们认为变量名表示的是数据本身,而函数名、字符串名和数组名表示的是代码块或数据块的首地址

    如果一个变量存储了一份数据的指针,我们就称它为指针变量。在C语言中,允许用一个变量来存放指针,这种变量称为指针变量。指针变量的值就是某份数据的地址,这样的一份数据可以是数组、字符串、函数,也可以是另外的一个普通变量或指针变量。

    2、定义指针变量

    与定义普通变量相似,但需要在变量前加 *

    datatype *name;

    *表示这是一个指针变量,datatype表示该指针变量所指向的数据的类型。

    int a = 100;
    int *p = &a;

    在定义指针变量 p 的同时对它进行初始化,并将变量 a 的地址赋予它,此时 p就指向了 a。值得注意的是,p 需要的一个地址,a 前面必须要加取地址符&,否则是不对的。

    int a;
    int *p; //定义指针变量时必须带*
    p = &a; //给指针变量赋值时不能带*

    指针变量也可以连续定义,例如:

    int *a, *b, *c;

    3、通过指针变量获取数据

    指针变量pointer存储了数据的地址,通过指针变量能够获得该地址上的数据,格式为:

    *pointer

    这里的*称为指针运算符,用来取得某个地址上的数据。

    *在不同的场景下有不同的作用:*可以用在指针变量的定义中,表明这是一个指针变量,以和普通变量区分开;使用指针变量时在前面加*表示获取指针指向的数据,或者说表示的是指针指向的数据本身。

    int *p, a; //定义指针变量p,整型变量a
    p=&a;     //为指针变量赋值
    *p=100;  //改变指针指向的数据值
     

     对*的总结

    1)表示乘法,例如int a = 3, b = 5, c;  c = a * b;,这是最容易理解的。
    2)表示定义一个指针变量,以和普通变量区分开,例如int a = 100;  int *p = &a;。
    3)表示获取指针指向的数据,是一种间接操作,例如int a, b, *p = &a;  *p = 100;  b = *p;

    4、指针变量的运算

    指针变量保存的是地址,而地址本质上是一个整数,所以指针变量可以进行部分运算,例如加法、减法、比较等(不能对指针变量进行乘法、除法、取余等其他运算,除了会发生语法错误,也没有实际的含义)。

    指针变量加减运算的结果跟数据类型的长度有关。

    5、指针数组

    数组(Array)是一系列具有相同类型的数据的集合,每一份数据叫做一个数组元素(Element)。数组中的所有元素在内存中是连续排列的,整个数组占用的是一块内存。定义数组时,要给出数组名和数组长度,数组名可以认为是一个指针它指向数组的第 0 个元素。在C语言中,我们将第 0 个元素的地址称为数组的首地址

    #include <stdio.h>
    
    int main(){
        int arr[] = { 99, 15, 100, 888, 252 };
        int len = sizeof(arr) / sizeof(int);  //求数组长度
        int i;
        for(i=0; i<len; i++){
            printf("%d  ", *(arr+i) );  //*(arr+i)等价于arr[i],arr 被转换成了一个指针
        }
        printf("
    ");
        return 0;
    }

    也可以定义一个指向数组的指针,例如:

    int arr[] = { 99, 15, 100, 888, 252 };
    int *p = arr;

    arr 本身就是一个指针,可以直接赋值给指针变量 p。arr 是数组第 0 个元素的地址,所以int *p = arr;也可以写作int *p = &arr[0];。也就是说,arr、p、&arr[0] 这三种写法都是等价的,它们都指向数组第 0 个元素,或者说指向数组的开头。

    如果一个指针指向了数组,我们就称它为数组指针(Array Pointer)。数组指针指向的是数组中的一个具体元素,而不是整个数组,所以数组指针的类型和数组元素的类型有关。

    引入数组指针后,我们就有两种方案来访问数组元素了,一种是使用下标,另外一种是使用指针。

    1) 使用下标

    也就是采用 arr[i] 的形式访问数组元素。如果 p 是指向数组 arr 的指针,那么也可以使用 p[i] 来访问数组元素,它等价于 arr[i]。

    2) 使用指针

    也就是使用 *(p+i) 的形式访问数组元素。另外数组名本身也是指针,也可以使用 *(arr+i) 来访问数组元素,它等价于 *(p+i)。

    不管是数组名还是数组指针,都可以使用上面的两种方式来访问数组元素。不同的是,数组名是常量,它的值不能改变,而数组指针是变量(除非特别指明它是常量),它的值可以任意改变。也就是说,数组名只能指向数组的开头,而数组指针可以先指向数组开头,再指向其他元素。

    借助自增运算符来遍历数组元素:

    #include <stdio.h>
    
    int main(){
        int arr[] = { 99, 15, 100, 888, 252 };
        int i, *p = arr, len = sizeof(arr) / sizeof(int);
    
        for(i=0; i<len; i++){
            printf("%d  ", *p++ );  //理解为*(p++)
        }
        printf("
    ");
        return 0;
    }

    c语言中自增运算符++和指针运算符*平级,如果两个同时出现,运算是从右往左(不是常规的从左往右):

    *p++ 等价于 *(p++),表示先取得第 n 个元素的值,再将 p 指向下一个元素,上面已经进行了详细讲解。
    
    *++p 等价于 *(++p),会先进行 ++p 运算,使得 p 的值增加,指向下一个元素,整体上相当于 *(p+1),所以会获得第 n+1 个数组元素的值。
    
    (*p)++ 就非常简单了,会先取得第 n 个元素的值,再对该元素的值加 1。假设 p 指向第 0  个元素,并且第 0 个元素的值为 99,执行完该语句后,第 0  个元素的值就会变为 100

     6、指针变量作为函数参数

    用指针变量作函数参数可以将函数外部的地址传递到函数内部,使得在函数内部可以操作函数外部的数据,并且这些数据不会随着函数的结束而被销毁。

    像数组、字符串、动态分配的内存等都是一系列数据的集合,没有办法通过一个参数全部传入函数内部,只能传递它们的指针,在函数内部通过指针来影响这些数据集合。

    #include <stdio.h>
    
    int max(int *intArr, int len){
        int i, maxValue = intArr[0];  //假设第0个元素是最大值
        for(i=1; i<len; i++){
            if(maxValue < intArr[i]){
                maxValue = intArr[i];
            }
        }
       
        return maxValue;
    }
    
    int main(){
        int nums[6], i;
        int len = sizeof(nums)/sizeof(int);
        //读取用户输入的数据并赋值给数组元素
        for(i=0; i<len; i++){
            scanf("%d", nums+i);
        }
        printf("Max value is %d!
    ", max(nums, len));
    
        return 0;
    }

    参数 intArr 仅仅是一个数组指针,在函数内部无法通过这个指针获得数组长度,必须将数组长度作为函数参数传递到函数内部。数组 nums 的每个元素都是整数,scanf() 在读取用户输入的整数时,要求给出存储它的内存的地址,nums+i就是第 i 个数组元素的地址。

    需要强调的是,不管使用哪种方式传递数组,都不能在函数内部求得数组长度,因为 intArr 仅仅是一个指针,而不是真正的数组,所以必须要额外增加一个参数来传递数组长度。

    7、指针作为函数的返回值

    C语言允许函数的返回值是一个指针(地址),我们将这样的函数称为指针函数。下面的例子定义了一个函数 strlong(),用来返回两个字符串中较长的一个:

    #include <stdio.h>
    #include <string.h>
    
    char *strlong(char *str1, char *str2){
        if(strlen(str1) >= strlen(str2)){
            return str1;
        }else{
            return str2;
        }
    }
    
    int main(){
        char str1[30], str2[30], *str;
        gets(str1);
        gets(str2);
        str = strlong(str1, str2);
        printf("Longer string: %s
    ", str);
    
        return 0;
    }

    用指针作为函数返回值时需要注意的一点是,函数运行结束后会销毁在它内部定义的所有局部数据,包括局部变量、局部数组和形式参数,函数返回的指针请尽量不要指向这些数据,C语言没有任何机制来保证这些数据会一直有效,它们在后续使用过程中可能会引发运行时错误。

    8、二级指针(指向指针的指针)

    指针可以指向一份普通类型的数据,例如 int、double、char 等,也可以指向一份指针类型的数据,例如 int *、double *、char * 等。

    如果一个指针指向的是另外一个指针,我们就称它为二级指针,或者指向指针的指针

    假设有一个 int 类型的变量 a,p1是指向 a 的指针变量,p2 又是指向 p1 的指针变量,它们的关系如下图所示:

    int a =100;
    int *p1 = &a;
    int **p2 = &p1;

    指针变量也是一种变量,也会占用存储空间,也可以使用&获取它的地址。C语言不限制指针的级数,每增加一级指针,在定义指针变量时就得增加一个星号*。p1 是一级指针,指向普通类型的数据,定义时有一个*;p2 是二级指针,指向一级指针 p1,定义时有两个*

    9、二维数组指针(指向二维数组的指针)

    二维数组在概念上是二维的,有行和列,但在内存中所有的数组元素都是连续排列的。例如:

    int a[3][4] = { {0, 1, 2, 3}, {4, 5, 6, 7}, {8, 9, 10, 11} };

    从概念上理解,a 的分布像一个矩阵:

    0   1   2   3
    4   5   6   7
    8   9  10  11

    但在内存中,a 的分布是一维线性的,整个数组占用一块连续的内存:

    C语言中的二维数组是按行排列的,也就是先存放 a[0] 行,再存放 a[1] 行,最后存放 a[2] 行;每行中的 4 个元素也是依次存放。数组 a 为 int 类型,每个元素占用 4 个字节,整个数组共占用 4×(3×4) = 48 个字节。

    C语言允许把一个二维数组分解成多个一维数组来处理。对于数组 a,它可以分解成三个一维数组,即 a[0]、a[1]、a[2]。每一个一维数组又包含了 4 个元素,例如 a[0] 包含 a[0][0]、a[0][1]、a[0][2]、a[0][3]。

    假设数组 a 中第 0 个元素的地址为 1000,那么每个一维数组的首地址如下图所示:

    先定义一个指向 a 的指针变量 p:

    int (*p)[4] = a;

    括号中的*表明 p 是一个指针,它指向一个数组,数组的类型为int [4],这正是 a 所包含的每个一维数组的类型。

    [ ]的优先级高于*( )是必须要加的,如果写作int *p[4],那么应该理解为int *(p[4]),p 就成了一个指针数组,而不是二维数组指针。

    对指针进行加法(减法)运算时,它前进(后退)的步长与它指向的数据类型有关,p 指向的数据类型是int [4],那么p+1就前进 4×4 = 16 个字节,p-1就后退 16 个字节,这正好是数组 a 所包含的每个一维数组的长度。也就是说,p+1会使得指针指向二维数组的下一行,p-1会使得指针指向数组的上一行。

    数组名 a 在表达式中也会被转换为和 p 等价的指针!

    #include <stdio.h>
    int main(){
        int a[3][4] = { {0, 1, 2, 3}, {4, 5, 6, 7}, {8, 9, 10, 11} };
        int (*p)[4] = a;
        printf("%d
    ", sizeof(*(p+1)));
    
        return 0;
    }

    *(p+1)单独使用时表示的是第 1 行数据,放在表达式中会被转换为第 1 行数据的首地址,也就是第 1 行第 0 个元素的地址,因为使用整行数据没有实际的含义,编译器遇到这种情况都会转换为指向该行第 0 个元素的指针;就像一维数组的名字,在定义时或者和 sizeof、& 一起使用时才表示整个数组,出现在表达式中就会被转换为指向数组第 0 个元素的指针。

    *(*(p+1)+1)表示第 1 行第 1 个元素的值。很明显,增加一个 * 表示取地址上的数据。

    a+i == p+i
    a[i] == p[i] == *(a+i) == *(p+i)
    a[i][j] == p[i][j] == *(a[i]+j) == *(p[i]+j) == *(*(a+i)+j) == *(*(p+i)+j)
    #include <stdio.h>  //使用指针遍历二维数组。
    int main(){
        int a[3][4]={0,1,2,3,4,5,6,7,8,9,10,11};
        int(*p)[4];
        int i,j;
        p=a;
        for(i=0; i<3; i++){
            for(j=0; j<4; j++) printf("%2d  ",*(*(p+i)+j));
            printf("
    ");
        }
    
        return 0;
    }

    指针数组和二维数组指针的区别:指针数组和二维数组指针在定义时非常相似,只是括号的位置不同:

    int *(p1[5]);  //指针数组,可以去掉括号直接写作 int *p1[5];
    int (*p2)[5];  //二维数组指针,不能去掉括号

    指针数组和二维数组指针有着本质上的区别:指针数组是一个数组,只是每个元素保存的都是指针,以上面的 p1 为例,在32位环境下它占用 4×5 = 20 个字节的内存。二维数组指针是一个指针,它指向一个二维数组,以上面的 p2 为例,它占用 4 个字节的内存。

    10、总结

    程序在运行过程中需要的是数据和指令的地址,变量名、函数名、字符串名和数组名在本质上是一样的,它们都是地址的助记符在编写代码的过程中,我们认为变量名表示的是数据本身,而函数名、字符串名和数组名表示的是代码块或数据块的首地址;程序被编译和链接后,这些名字都会消失,取而代之的是它们对应的地址。

    1) 指针变量可以进行加减运算,例如p++p+ip-=i。指针变量的加减运算并不是简单的加上或减去一个整数,而是跟指针指向的数据类型有关。


    2) 给指针变量赋值时,要将一份数据的地址赋给它,不能直接赋给一个整数,例如int *p = 1000;是没有意义的,使用过程中一般会导致程序崩溃。

    3) 使用指针变量之前一定要初始化,否则就不能确定指针指向哪里,如果它指向的内存没有使用权限,程序就崩溃了。对于暂时没有指向的指针,建议赋值NULL

    4) 两个指针变量可以相减。如果两个指针变量指向同一个数组中的某个元素,那么相减的结果就是两个指针之间相差的元素个数。

    5) 数组也是有类型的,数组名的本意是表示一组类型相同的数据。在定义数组时,或者和 sizeof、& 运算符一起使用时数组名才表示整个数组,表达式中的数组名会被转换为一个指向数组的指针。


    学习参考:http://c.biancheng.net/view/2026.html

  • 相关阅读:
    xmlTextTextReaderNodeType来读取XML元素的类型
    [转]关于两个坐标点的距离的计算问题
    Incorrect decrement of the reference count of an object that is not owned at this point by the caller1
    让ipad时时显示内存剩余量
    【转】苹果开发者账号注册流程
    自定义百度地图气泡
    [转] iOS 常用数学函数
    [转]JDK环境变量的配置
    纬度在换算距离上一度等于多少公里?
    [转]UIDevice uniqueGlobalDeviceIdentifier(百度地图API的那些事)
  • 原文地址:https://www.cnblogs.com/lemonzhang/p/12303119.html
Copyright © 2011-2022 走看看