iOS C语言之指针

概览

指针是C语言的精髓,但是很多初学者往往对于指针的概念不是很深刻,以至于学完之后随着时间的推移越来越模糊,感觉指针难以掌握,本文通过简单的例子试图将指针解释清楚,今天的重点有几个方面:

  1. 什么是指针
  2. 数组和指针
  3. 字符串和指针
  4. 函数和指针

什么是指针

存放变量地址的变量我们称之为“指针变量”,简单的说变量p中存储的变量a的地址,那么p就可以成为是指针变量,或者说p指向a。
当我们访问a变量的时候,其实是程序先根据a取得a对应的地址,再到这个地址对应的存储空间中拿到a的值,这种方式我们称之为“直接引用”;
当我们通过p取得a的时候,首先要先根据p转换成p对应的存储地址,再根据这个地址到其对应的存储空间中拿到存储内容,它的内容其实就是a的地址,然后根据这个地址到对应的存储空间中取得对应的内容,这个内容就是a的值,这种通过p找到a对应地址再取值的方式称为“间接引用”。

a、p存储地址例子

接下来,我们来看一下指针的赋值

//
//  main.c
//  Pointer
//
//  Created by LOLITA on 2017/8/7.
//  Copyright © 2017年 LOLITA0164. All rights reserved.
//

#include <stdio.h>


int main(int argc, const char * argv[]) {

    int a = 1;
    int *p = &a;
    printf("address(a)=%p,address(p)=%p\n",&a,p); //结果:address(a)=0x7fff5fbff72c,address(p)=0x7fff5fbff72c
    printf("a=%d,*p=%d\n",a,*p); //结果:a=1,p=1

    *p = 2;
    printf("a=%d,*p=%d\n",a,*p);  //结果:a=2,p=2




    int b = 8;
    char c = 1;
    int *q = &c; // 这样写系统会爆黄
    printf("address(b)=%p,address(c)=%p\n",&b,&c); //结果:address(b)=0x7fff5fbff71c,address(c)=0x7fff5fbff71b
    printf("c=%d,q=%d\n", c, *q); //结果:c=1,q=2049,为什么q的值不是1呢?

    return 0;
}

说明:

a、int *p;中的*只是表示p变量是一个指针变量;而打印*p的时候,*p中的*是操作符,表示p指针指向的变量的存储空间(当前存储就是1),同时我们也看到了*p==a;当我们修改了*p时,也就修改了p指向的存储空间的内容,即修改了a,所以第二次打印a==2;

b、指针所指向的类型必须和定义指针时声明的类型相同;上面指针q定义成了int型而指向了char型,结果输出*q时,打印出了2049,具体原因见下图

例图

由于局部变量是存储在栈里面的,所以先存储b再存储a、p,当打印*p的时候,其实就是以p指向的地址对应的空间开始取两个字节的数据,刚好定义的b和c空间连续,所以就取到了b的一个字节,最后*p二进制存储为“0000100000000001”(见上面黄色背景内容),十进制表示就是2049

c、指针变量占用的空间和它所指向的变量类型无关,只跟编译器位数有关(准确的说只跟寻址方式有关)


数组和指针

由于数组的存储是连续的,数组名就是数组的地址,这样一来数组和指针就有着很微妙的关系,先看下面的例子:

//
//  main.c
//  Pointer
//
//  Created by LOLITA on 2017/8/7.
//  Copyright © 2017年 LOLITA0164. All rights reserved.
//

#include <stdio.h>

void changeValue1(int a[]){
    a[0] = 2; // a表示数组首地址,a[i]表示直接访问
}

void changeValue2(int *p){
    p[0] = 3; // p指向数组首地址,p[0]表示间接访问
}


int main(int argc, const char * argv[]) {

    int a[] = {1,2,3};
    int *p = &a[0]; // 等价于*p=a;

    // 指针加1表示地址向后nuodong所指向类型的长度位
    // 也就是说p指向a[0],p+1指向a[1],依次类推,所以我们通过指针也可以取出数组元素
    for (int i = 0; i<3; i++) {
        // printf("a[%d]=%d\n",i,a[i]);
        printf("a[%d]=%d\n",i,*(p+i));  // 由于a就代表数组的地址,其实这里还可以写成*(a+i),但是注意这里*(p+i)可以写成*(p++),但是*(a+i)不能写成*(a++),因为数组名是常量
    }
    /*输出结果:
     a[0]=1
     a[1]=2
     a[2]=3
     */

    changeValue1(p);  //等价于:changeValue(a)
    for(int i=0;i<3;++i){
        printf("a[%d]=%d\n",i,a[i]);
    }
    /*输出结果:
     a[0]=2
     a[1]=2
     a[2]=3
     */

    changeValue2(a); //等价于:changeValue2(p)
    for(int i=0;i<3;++i){
        printf("a[%d]=%d\n",i,a[i]);
    }
    /*输出结果:
     a[0]=3
     a[1]=2
     a[2]=3
     */


    return 0;
}

从上面的例子我们可以得出如下结论:

  1. 数组名a==&a[0]==*p; // 这里的*表示指针的声明,而非取数据;
  2. 如果p指向一个数组,那么p+1表示指向数组的下一个元素,同时p+1移动的长度不是固定的,需要根据p指向的数据类型而定;
  3. 指针可以写成p++形式,但是数组名不可以,因为数组名是常量;
  4. 不管函数的形参为数组还是指针,实参都可以使用数组名或者指针。

字符串和指针

由于在C语言中字符串就是字符数组,下面不妨看一字符串和数组的关系:

//
//  main.c
//  Pointer
//
//  Created by LOLITA on 2017/8/7.
//  Copyright © 2017年 LOLITA0164. All rights reserved.
//

#include <stdio.h>


int main(int argc, const char * argv[]) {


    char a[] = "LOLITA0164";

    printf("%p,%s\n",a,a);//结果:0x7fff5fbff72d,LOLITA0164,同一个变量a是输出字符串还是输出地址,根据格式参数而定



    char b[] = "LOLITA0164";
    char *p = b;
    printf("b=%s,p=%s\n",b,p);//结果:b=LOLITA0164,p=LOLITA0164

    // 指针存储的是地址,er数组名存储的也是地址,既然字符数组可以表示字符串,那么指向字符的指针同样也可以
    char *c = "LOLITA"; //等价于char c[]="LOLITA";
    printf("c=%s\n",c);


    return 0;
}

函数和指针

在弄清楚函数指针的问题之前,我们不妨先来看一下指针类型函数,毕竟指针类型也是C语言数据类型,下面以一个字符串换为大写字符串的程序为例,在这个例子中不仅可以看到啊返回值为指针类型的函数同时还可以看到前面说到的指针移动操作:

//
//  main.c
//  Pointer
//
//  Created by LOLITA on 2017/8/7.
//  Copyright © 2017年 LOLITA0164. All rights reserved.
//

#include <stdio.h>

char * toUpper(char *a){
    char *b = a;  // 保留最初地址,因为后面的循环会改变字符串最初地址
    int len = 'a' - 'A'; // 大小写ASCII码差值相等
    while (*a != '\0') { // 字符是否结束
        if (*a >= 'a' && *a <= 'z') { // 如果是小写字符
            *(a++) -= len; // *a表示数组对应的字符(-32转换为小写),a++表示移动到下一个字符
        }else{
            a++; // 如果不是小写字符,移动到下一个字符
        }
    }
    return b;
}

int main(int argc, const char * argv[]) {

    char a[] = "loLitA";
    char *p = toUpper(a);
    printf("%s\n",p);

    return 0;
}

大家都知道函数只能有一个返回值,如果需要返回多个之,怎么办?其实很简单,只要将指针作为函数参数传递就可以了。下面的例子中我们可以再次看到指针作为参数进行传递:

//
//  main.c
//  Pointer
//
//  Created by LOLITA on 2017/8/7.
//  Copyright © 2017年 LOLITA0164. All rights reserved.
//

#include <stdio.h>

int operate(int a,int b, int *c){
    *c = a - b; // 指针c指向a-b的结果地址
    return a+b;
}

int main(int argc, const char * argv[]) {

    int a=1,b=2,c,d;

    d = operate(a, b, &c);

    printf("a+b=%d,a-b=%d\n",d,c);//结果:a+b=3,a-b=-1

    return 0;
}

函数也是在内存中存储的,当然函数也有一个起始地址(事实上函数名就是函数的起始地址),这是同样需要弄清楚函数指针的关系。函数指针定义的形式
:返回类型(*指针变量名)(新参列表)
,拿到函数指针其实我们就相当于拿到了这个函数,函数的操作都可以通过指针来完成,而且通过前面的例子可以看到指针作为C语言的数据类型,可以作为参数,返回值,那么函数指针当然同样可以作为函数的参数和返回值:

//
//  main.c
//  Pointer
//
//  Created by LOLITA on 2017/8/7.
//  Copyright © 2017年 LOLITA0164. All rights reserved.
//

#include <stdio.h>

int sum(int a,int b){
    return a+b;
}

int sub(int a,int b){
    return a-b;
}

// 函数指针作为参数进行传递
int operate(int a,int b, int (*p)(int,int)){ // int (*p)(int,int) 为指针类型
    return p(a,b);
}

int main(int argc, const char * argv[]) {

    int a=1,b=2;
    int (*p)(int,int) = sum; // 函数名就是函数首地址,等价于 int (*p)(int,int);p=sum;
    int c = p(a,b);
    printf("a+b=%d\n",c);


    // 函数作为参数传递
    printf("%d\n",operate(a, b, sum)); //结果:3
    printf("%d\n",operate(a, b, sub)); //结果:-1


    return 0;
}

函数指针可以作为函数参数进行传递,实在太强大了,是不是想起了C#中的委托?记得C#书籍中经常提到委托类似于函数指针,其实说的就是上面的情况。需要注意的是,普通的指针可以写成p++进行移动,而函数指针写成p++并没有意义。


鸣谢

文本转载自 崔江涛(KenshinCui)文中的一篇iOS开发系列–C语言之指针

非常感谢作者,本已经模糊了指针的概念,通过本文详细的讲解,穿插demo例子,让我再次理解了指针的概念,也非常推荐作者的文章,写得非常详细而且全面!

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值