目录
  1. 1. 指针
    1. 1.1. 内存、地址和指针
      1. 1.1.1. 内存
      2. 1.1.2. 地址
      3. 1.1.3. 指针
        1. 1.1.3.1. 定义
        2. 1.1.3.2. 指针的运算符
        3. 1.1.3.3. 指针表达式
        4. 1.1.3.4. 指针和数组
        5. 1.1.3.5. 指针和函数
    2. 1.2. 结束语
学习C语言 指针

指针

指针,是C语言中的一个重要特性。运用好指针,我们对于内存工作的理解也会有深刻的认识。

内存、地址和指针

内存

![内存](内存.png “内存”)
如上图所示,这就是内存在计算机中的表现形式,每一个单位称为一个字节,每个字节包含存储一个字符所需要的位数。
![内存单位](内存单位.png “内存单位”)
为了存储更大的值,多个字节组合成一个更大的单位。上图是以4个字节的字来表示。

  • 注意:
    1.尽管一个字包含4个字节,但它仍然只有一个地址。
    2.内存中的每个位置(一个地址)有一个独一无二的标识
    3.内存中的每个位置(一个地址)都包含一个值

地址

下面一个例子显示了内存中5个字的内容
内存内容
方框上方是每个字的在内存中的地址,方框中的值就是这个地址中的内容
高级语言的特性就是通过名字来访问内存的位置,高级语言使用名字来代替地址,这些名字就是变量。
指针变量,则是指向这些名字的变量(指向变量的变量),指针变量指向一个内存地址。通过取地址运算符指向名字所代表的地址,通过解引用得到这个内存地址中的内容。

指针

定义
  C语言中的指针是指专门用来存放内存地址的变量,每个指针都有与之相关联的数据类型,该类型决定了指针所指向的变量的类型。
1
2
3
char *p ;         //定义了一个char类型的指针变量,这个指针变量能指向一个char类型的内存地址
int *p1 , p2 ; //这里定义了一个指针int型的指针变量p1和一个int变量p2
double *p=null ; //这个语句可以看作是两个语句,先是定义double类型的指针变量p,将p指向一个内存地址(这里为NULL表示不指向任何地址)
指针的运算符
1
2
&: 取地址运算符 , 指针通过取地址运算符得到变量名代表的地址
*: 解引用运算符 ,指针通过解引用运算符得到该地址中的内容
指针表达式

以一个声明开头,首先声明和初始化需要的变量

1
2
char ch = 'd';
char *cp = &ch ;
  • 注意
    以下图中加粗的椭圆表示表达式的值
    方框表示地址
    方框上的字符表示内存别名

  • &ch表达式
    &ch表达式
    解释:表达式&ch意思为将变量ch取地址,得到ch的地址,这个地址中的内容就是’d’。当&ch用作右值时,取的是ch的地址;作为左值则是非法(左值一般是变量,用于存储右值,&ch会取得一个地址,不是变量)。

  • cp表达式
    cp表达式
    解释:表达式cp是一个指针变量(指向一个变量的变量),在声明中已经为cp指向&ch。当作为左值时,它的左值就是cp所处的内存位置;当作为右值就是cp的内容。

  • &cp表达式
    &cp表达式
    解释:表达式&cp是将指针变量cp取地址,得到指针变量的地址。cp是一个指向ch地址的指针变量,&cp作为右值则是一个不知名的指针(指向字符d的指针的指针)指向的地址。同样,不能作为左值。

  • *cp表达式
    *cp表达式
    解释:表达式*cp是将指针cp解引用,得到ch地址中的内容’d’。当作为右值时,它的值是ch中的内容;作为左值是得到它自己的地址,*cp的地址是ch的地址;作为右值则是它自己的内容,它的内容是ch的内容。

  • *cp+1
    *cp+1表达式
    解释:*cp+1,cp先解引用得到cp指向地址的内容’d’,然后+1,得到一个新值’e’。当作为左值时,表达式是一个具体的值,不能作为左值;作为右值,它的值是一个新值’e’。

  • *(cp+1)
    *(cp+1)
    解释:*(cp+1),得到一个新的指针cp+1,指向一个新的地址,然后将指针解引用,得到指向的新地址的内容。作为右值时,表示这个cp+1指向的新的地址的内容;作为左值时,表示cp+1指向的新的地址本身。

  • ++cp
    ++cp
    解释: ++cp,可以分为两个变量,增值前的指针和增值后的指针。增值前的指针先增加得到一个新的指针指向一个新的地址,增加后的指针得到增加前的指针+1后的一份拷贝,增加后的指针也是指向同一个新地址。

  • cp++
    cp++
    解释: cp++,先返回一份cp的拷贝,然后再增加cp的值,表达式的值则是原来cp的值的一份拷贝。

  • *++cp
    cp++
    解释: 间接访问操作符作用于增值后的指针的拷贝上,所以它的右值是ch后面的那个内存地址的值,左值是是那个位置本身。

  • *cp++
    *cp++
    解释: *cp++,右值是变量ch的值,左值是变量ch的内存位置。

  • ++*cp
    ++*cp
    解释: 先执行间接访问操作,然后,cp所指向的的值增加1。

指针和数组

C语言中,指针和数组密切相关,数组名实际上是指向数组第一个元素的指针

1
2
3
4
int a[] = {1,2,3,4,5,6}; //一个数组在内存中表示一个逻辑上连续存储的内存块
int *p = a ; //这里指针p指向的是一个数组的首地址(也就是一个内存块的地址),通过指向首地址而指向一个数组
int *pa = &a[0]; //这里pa指针指向的是数组a中的第一个元素的地址
p = p+1 ; //指针此时,p指向数组a中的a[1]的地址

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#include <stdio.h>
int main()
{
int a[10],i , *p=NULL ; //这里定义一个长度为10的数组a,一个变量i,和一个初始化为NULL的指针p

//初始化数组a
for(i=0;i<10;i++)
{
a[i] = i ;
}

//用下标方法打印数组值(数组从0开始)
for(i=0 ; i<10 ; i++)
{
printf("%d",a[i]);
}
printf("\n");

//用指针形式打印数组,a+i表示从首地址开始加i个地址,相当于得到数组中的第i个元素的地址,然后用*解引用运算符解引用,得到a+i地址中的内容
for(i=0 ; i<10 ; i++)
{
printf("%d",*(a+i));
}
printf("\n");

//先让指针p指向数组a的首地址,使得指针p指向数值a
//*p++ : *p=*p+1由于运算符+优先级小于* , 指针p先解引用得到p指向地址的内容,并打印,再+1,将新值赋值给*p,之后指针p指向下一个地址
for(p=a ; p<a+10 ;)
{
printf("%d",*p++);
}
printf("\n");

return 0 ;
}

  • 注意区别:
    *p++ : 先取得当前p所指向的变量值再使得p指向后一个变量,相当于a[i++]
    *p-- : 先取得当前p所指向的变量值再使得p指向前一个变量,相当于a[i–]
    *++p : 先使p指向后一个变量再取得p所指向的变量的值,相当于a[++i]
    *–p : 先使p指向前一个变量再取得p所指向的变量的值,相当于a[–i]
指针和函数
  • 指针作为函数的参数
    作用是将一个变量的地址送到一个函数中。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    #include <stdio.h>

    void change(int i , int *p)
    {
    i++ ;
    if(p!=NULL)
    {
    (*p)++; //先解引用取得当前指向的变量值,再将值+1,然后指向新的值(与*p++有区别)
    }
    }

    int main()
    {
    int a=5,b=10;
    change(&b);
    printf("a=%d,b=%d\n",a,b);//a=5,b=11;a传进函数后,成为这个函数的局部变量,在函数销毁后,局部变量得到的值也会被销毁;b是将地址传进函数,通过指针运算,指针p将b的地址中的值+1,最终函数销毁,b的地址没有被销毁,因此b的值变了
    return 0 ;
    }
  • 返回指针的函数
    作用是返回指针,即地址(指针指向的是地址)。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    #include <stdio.h>
    char *name[3] = {"are","you","ok"};//一个字符串需要用指针来获取
    char *message = "wrong input" ;

    char *test(int i)
    {
    if(i<0 || i>3)
    {
    return message ;//返回一个指向字符串的指针
    }
    else
    {
    return name[0] ;//返回数组的首地址
    }
    }

    int a ;
    char *p ;
    printf("input:");
    scanf("%d",&a);
    p = test(a); //p获取返回的字符串
    printf("%s\n",p);
  • 指向函数的指针
    指向函数的指针是一个指针变量,它 指向一个函数。一个函数的 函数名是一个指针,它指向 函数的代码。函数的调用可以通过函数名来调用也可以通过指向函数的指针调用。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    #include <stdio.h>
    #define GET_MAX 0
    #define GET_MIN 1

    //定义一个获取最大值函数
    int get_max(int i , int j)
    {
    return i>j?i:j ;
    }
    //定义一个获取最小值函数
    int get_min(int i , int j)
    {
    return i>j?j:i ;
    }

    int compare (int i , int j , int flag)
    {
    int ret ;
    int (*p)(int , int); //定义一个指向有两个int参数的函数的函数指针
    if(flag == GET_MAX) //如果为真
    {
    //笔者注:可以把p看做是引用变量,指向一个函数对象
    p = get_max ; //使得函数指针p指向函数get_max的入口地址,让函数指针作为get_max函数的别名
    }
    else
    {
    p = get_min ;
    }

    //笔者注:有点像面对对象的多态,p就是一个接口,而get_max和get_min函数就是这个接口的具体实现,需要用到那个函数就使用p调用哪个函数
    ret = p(i,j);//将参数传入函数指针,通过函数指针p来调用p指向的函数,获取返回值
    return ret ;
    }

    int main()
    {
    int i=5 , j =10 ,ret ;
    ret = compare(i,j,GET_MAX);
    printf("the MAX is %d\n",ret);
    return 0 ;
    }
  • 函数指针做形参
    函数的参数可以是指针,那么指向函数的函数指针也可以作为函数参数传递。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    #include <stdio.h>
    int get_big(int i , int j)
    {
    return i>j?i:j ;
    }
    //笔者注:其实可以把函数形参中的函数指针放在函数中,作为局部变量
    int get_max(int i , int j , int k , int (*p)(int , int))
    {
    int ret ;
    ret = p(i,j);
    ret = p(ret , k);
    return ret ;
    }
    //函数指针作为局部变量
    int get_max1(int i, int j , int k)
    {
    int (*p)(int ,int);
    int ret ;
    p = get_big ;
    ret = p(i,j);
    ret = p(ret,k);
    return ret ;
    }

    int main()
    {
    int i=5,j=10,k=15,ret1,ret2 ;
    //两个方法都可行
    ret1 = get_max(i,j,k,get_big);
    ret2 = get_max1(i,j,k);
    printf("get_max:%d\nget_max1%d\n",ret1,ret2);
    return 0 ;
    }
  • 返回函数指针的函数

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    #include <stdio.h>
    //笔者注:将get_big这个函数看作是商品
    int get_big(int i , int j)
    {
    return i>j?i:j ;
    }

    //笔者注:可以把这个返回函数指针的函数看作是快递员,商品通过快递员运送
    int (*get_function(int a))(int , int)
    {
    printf("%d\n",a);
    return get_big ; //返回一个函数指针,将函数返回
    }

    int main()
    {
    int i=5 , j = 10 , max ;
    int (*p)(int , int); //定义一个函数指针
    //笔者注:最后快递员将商品运送到客户那里,100就是商品的快递号
    p = get_function(100); //用函数指针指向返回函数指针的函数的返回值,得到一个函数,现在p是指向get_big函数的函数指针
    //笔者注:客户开始使用商品
    max = p(i,j); //现在的p调用一个函函数
    printf("The MAX is %d\n",max);
    return 0 ;
    }
  • 指向字符串的指针
    C语言中没有字符串类型的关键字,但是有多种方法访问字符串。

    • 用字符数组存放字符串
    • 用字符串指针来指向字符串

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include <stdio.h>
int main()
{
//采用字符数组拷贝字符串
char a[] = "Linux C Program" , b[20],c[20];
int i ;
//数组采用指针形式
for(i=0 ; *(a+i) != '\0' l i++)
{
*(b+i) = *(a+i);
}
*(b+i) = '\0' ;
//采用字符指针拷贝字符串
char *p1 , *p2 ;
p1 = a ; //p1指向数组a首地址
p2 = c ;
//使用解引用运算符获取指向数组中的值
//对指针变量进行增量加,实现对数组块的地址遍历
for(;*p1 != '\0' ; p1++,p2++)
{
*p2=*p1 ; //将指针p1指向的数组中的元素的值传给指针p2指向的数组的元素
}
*p2='\0';

printf("%s\n",a);
printf("%s\n",b);
printf("%s\n",c);
return 0 ;
}

结束语

指针作为C语言的精髓,对于初学者来说确实难学。难学归难学,在真正理解并接受指针这个概念后,你将被指针的绝妙所折服。正所谓,不识庐山真面目,只缘身在此山中。

文章作者: rack-leen
文章链接: http://yoursite.com/2019/04/21/C/%E5%AD%A6%E4%B9%A0C%E8%AF%AD%E8%A8%80/%E5%AD%A6%E4%B9%A0C%E8%AF%AD%E8%A8%80-%E6%8C%87%E9%92%88/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 rack-leen's blog
打赏
  • 微信
  • 支付宝

评论