IOS开发之路--C语言指针

概览

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

什么是指针 数组和指针 函数指针

什么是指针

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

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

//
// main.c
// Point
//
// Created by Kenshin Cui on 14-7-05.
// Copyright (c) 2014年 Kenshin Cui. All rights reserved.
//

#include <stdio.h>

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

  int a=1;
  int *p;
  p=&a; //也可以直接给指针变量赋值:int *p=&a;
  printf("address(a)=%x,address(p)=%x\n",&a,p); //结果:address(a)=5fbff81c,address(p)=5fbff81c
  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)=%x,address(c)=%x\n",&b,&c);//结果:
  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,具体原因见下图(假设在16位编译器下,指针长度为2字节)

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

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

数组和指针

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

//
// main.c
// Point
//
// Created by Kenshin Cui on 14-7-05.
// Copyright (c) 2014年 Kenshin Cui. All rights reserved.
//

#include <stdio.h>

void changeValue(int a[]){
  a[0]=2;
}
void changeValue2(int *p){
  p[0]=3;
}

int main(int argc, const char * argv[]) {
  int a[]={1,2,3};
  int *p=&a[0]; //等价于:*p=a;

  printf("len=%lu\n",sizeof(int));//取得int长度为2

  //指针加1代表地址向后挪动所指向类型的长度位(这里类型是int,长度为2)
  //也就是说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
   */

  changeValue(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;
}

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

数组名a==&a[0]==*p; 如果p指向一个数组,那么p+1指向数组的下一个元素,同时注意p+1移动的长度并不固定,具体需要根据p指向的数据类型而定; 指针可以写成p++形式,但是数组名不可以,因为数组名是常量 不管函数的形参为数组还是指针,实参都可以使用数组名或指针;扩展--字符串和指针

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

//
// main.c
// Point
//
// Created by Kenshin on 14-7-05.
// Copyright (c) 2014年 Kenshin Cui. All rights reserved.
//

#include <stdio.h>

int main(int argc, const char * argv[]) {
  char a[]="Kenshin";
  printf("%x,%s\n",a,a);//结果:5fbff820,Kenshin,同一个变量a是输出字符串还是输出地址,根据格式参数而定
  printf(a); //结果:Kenshin
  printf("\n");

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

  //指针存储的是地址,而数组名存储的也是地址,既然字符数组可以表示字符串,那么指向字符的指针同样也可以,如下方式可以更简单的定义一个字符串
  char *c="Kenshin"; //等价于char c[]="Kenshin";
  printf("c=%s\n",c); //结果:c=Kenshin

  return 0;
}

以上代码中注释基本已经很清楚了,这里需要指出是为什么printf(a)能够直接输出字符串呢?

我们看一下printf()的定义:int printf(const char * __restrict, ...) __printflike(1, 2);

其实printf的参数要求是指向字符类型的指针,而结合上面的例子和我们之前说的,如果函数形参是指针类型那么可以传入函数名,因此也就能正确输出字符串的内容了。类似的还有上一篇文章中说的strcat()、strcpy()等函数均是如此。

函数指针

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

//
// main.c
// Point
//
// Created by Kenshin Cui on 14-06-28.
// Copyright (c) 2014年 Kenshin Cui. 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++代表移动到下一个字符
    }
  }
    return b;
}

int main(int argc, const char * argv[]) {
  char a[]="hello";
  char *p=toUpper(a);
  printf("%s\n",p); //结果:HELLO
  return 0;
}

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

//
// main.c
// Point
//
// Created by Kenshin Cui on 14-6-28.
// Copyright (c) 2014年 Kenshin Cui. All rights reserved.
//

#include <stdio.h>

int operate(int a,int b,int *c){
  *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;
}

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

//
// main.c
// Point
//
// Created by Kenshin Cui on 14-6-28.
// Copyright (c) 2014年 Kenshin Cui. 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)){
  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); //结果:a+b=3

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

  return 0;
}

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

(0)

相关推荐

  • C语言中6组指针和自增运算符结合方式的运算顺序问题

    在C语言中,当指针运算符和++或者–结合时很容易分不清运算顺序,在这里总结一下,下面一共分析6中组合: * p++,(* p)++,* (p++),++* p,++( * p), * (++p). 先看段代码以及输出: #include<stdio.h> int main() { int a[3]={1,3,5}; int *p=a; printf("----------------1----------------\n"); printf("%d\n"

  • C语言中二维数组指针的简要说明

    C语言中,指针是一个复杂但又灵活多变的知识点,我们知道,在一维数组中,对于一个数组a[],*a,a,&a,都表示a的首地址,但如果与二维数组混合使用,就显得更为复杂了.例如对于一个二维数组 a[2][4]={{1,2.3},{4,5,6}} a+i,&a[i],*(a+i),a[i], 这四个表达式到底表示什么呢? 先告诉答案吧,其实这几个表达式都是指向同一个地址的,也许你会很诧异,也会很疑惑,怎么会是这样呢!!事实证明就是这样的, C语言中,指针是一个复杂但又灵活多变的知识点,我们知道,

  • C语言中常量指针与指针常量区别浅析

    常量指针是指--指向常量的指针,顾名思义,就是指针指向的是常量,即,它不能指向变量,它指向的内容不能被改变,不能通过指针来修改它指向的内容,但是指针自身不是常量,它自身的值可以改变,从而指向另一个常量.指针常量是指--指针本身是常量.它指向的地址是不可改变的,但地址里的内容可以通过指针改变.它指向的地址将伴其一生,直到生命周期结束.有一点需要注意的是,指针常量在定义时必须同时赋初值.注:也有人将这两个名称的定义与含义反过来认为:"指针常量:顾名思义它的中心词是"常量"这是重点

  • C语言的指针类型详细解析

    指针存储了内存的地址,同时指针是有类型的,如int*,float*,那么,一个自然的猜想就是指针变量应该存储这两方面的信息:地址和指针类型,比如,就像下面的结构体: 复制代码 代码如下: struct pointer{    long address;    int type;} 举个例子:打印sizeof(int*),值为4,可见4字节是存储内存地址用的,反过来就说明指针并没有存储类型信息的地方,那么指针的类型信息存放在哪儿呢?下面剖析一段简单的代码. 复制代码 代码如下: // ma.cpp

  • c语言指针之二级指针示例

    二级指针的概念 首先任何值都有地址,一级指针的值虽然是地址,但这个地址做为一个值亦需要空间来存放,是空间就具有地址,这就是存放地址这一值的空间所具有的地址,二级指针就是为了获取这个地址,一级指针所关联的是其值(一个地址)名下空间里的数据,这个数据可以是任意类型并做任意用途,但二级指针所关联的数据只有一个类型一个用途,就是地址,指针就是两个用途提供目标的读取或改写,那么二级指针就是为了提供对于内存地址的读取或改写指针的表现形式是地址,核心是指向关系指针运算符"*"的作用是按照指向关系访问

  • C语言入门之指针用法教程

    本文针对C语言初学者详细讲述了指针的用法,并配以实例进行说明.具体分析如下: 对于C语言初学者来说,需要明白指针是啥?重点就在一个"指"上.指啥?指的地址.啥地址?内存的地址. 上面说明就是指针的本质了. 这里再详细解释下.数据存起来是要存在内存里面的,就是在内存里圈出一块地,在这块地里放想放的东西.变量关心的是这块地里放的东西,并不关心它在内存的哪里圈的地:而指针则关心这块地在内存的哪个地方,并不关心这块地多大,里面存了什么东西. 指针怎么用呢?下面就是基本用法: int a, b,

  • C语言指针的长度和类型深入分析

    指针是C语言的精髓,本文就以实例的形式详细分析了C语言的长度和类型.对于初学者深入理解C语言程序设计有很好的参考价值.具体分析如下: 一般来说,如果考虑应用程序的兼容性和可移植性,指针的长度就是一个问题,在大部分现代平台上,数据指针的长度通常是一样的,与指针类型无关,尽管C标准没有规定所有类型指针的长度相同,但是通常实际情况就是这样.但是函数指针长度可能与数据指针的长度不同. 指针的长度取决于使用的机器和编译器,例如:在现代windows上,指针是32位或是64位长 测试代码如下: #inclu

  • 整理C语言中各种类型指针的特性与用法

    指针为什么要区分类型: 在同一种编译器环境下,一个指针变量所占用的内存空间是固定的.比如,在16位编译器环境 下,任何一个指针变量都只占用8个字节,并不会随所指向变量的类型而改变. 虽然所有的指针都只占8个字节,但不同类型的变量却占不同的字节数. 一个int占用4个字节,一个char占用1个字节,而一个double占用8字节: 现在只有一个地址,我怎么才能知道要从这个地址开始向后访问多少个字节的存储空间呢,是4个,是1个,还是8个. 所以指针变量需要它所指向的数据类型告诉它要访问多少个字节存储空

  • C语言中的数组和指针汇编代码分析实例

    今天看<程序员面试宝典>时偶然看到讲数组和指针的存取效率,闲着无聊,就自己写了段小代码,简单分析一下C语言背后的汇编,可能很多人只注重C语言,但在实际应用当中,当出现问题时,有时候还是通过分析汇编代码能够解决问题.本文只是为初学者,大牛可以飘过~ C源代码如下: 复制代码 代码如下: #include "stdafx.h" int main(int argc, char* argv[]) {        char a=1;        char c[] = "

  • 深入学习C语言中的函数指针和左右法则

    通常的函数调用     一个通常的函数调用的例子: //自行包含头文件 void MyFun(int x); //此处的申明也可写成:void MyFun( int ); int main(int argc, char* argv[]) { MyFun(10); //这里是调用MyFun(10);函数 return 0; } void MyFun(int x) //这里定义一个MyFun函数 { printf("%d\n",x); } 这个MyFun函数是一个无返回值的函数,它并不完成

随机推荐