指针的深度运用

1 篇文章 0 订阅
1 篇文章 0 订阅

指针一直是我很喜爱的一个工具,个人认为它对内存的操作比任何语言都要来得直截了当,浅显易懂。但今天在读TI的代码时,学习他们对于内存分配管理的方法,发现自己对指针的运用还算不上“熟悉”,对于目前我的开发,对指针的使用仅仅局限于对变量、数组、函数的地址引用,例如:

int *ptr;

char *func(int );

诸如此类简单的定义方式(当然,因为做过一些开发,多维的指针也可以玩玩),但是假如想发一个函数放到自己的结构体中呢,或者把多个函数归纳到一个数组中呢,我们可以定义一个数组arr[5],这种方式相当于控制了5个变量,为什么不能定义一个函数数组 func[5] 来控制5不同的函数呢;此外,如struct类型,我们可以把不同类型的变量放到一个结构体中,那么函数能不能放进去呢?

读代码的时候所带来的这一系列问题才猛然发现,基础!基础!看来我对C的理解仅停留在皮毛,看着别人精美绝伦的代码,才发现自己是多么的渺小,所以今天又看了很多讲指针的文章恶补一番,自己做个总结。


【有关指针的定义】

注意:一个指针被定义之后,它存在与栈中初值为0,不管你想象中它有多大的指向空间,它的长度都为4个字节,因此必须用malloccallocrealloc等宏指令来为其分配堆内存之后才可使用,否则就“段错误”,而数组一旦被定义即在堆中分配了连续的内存块,这也是指针和数组之间的一个不同之处。

一些约定

  • 指向变量指针所指向的那个变量
  • 指向指针:指针所指向的那个指针

 

  1. 变量的定义

关于变量的定义可能是指针运用里面最简单的一部分,因为它并不涉及到太多复杂的逻辑思维或内存管理,而在实际应用中,指针变量往往也只是负责承载如数组、函数、结构体或其他数据结构的首地址。

来看几个例子:

int *p;

定义一个指针变量,p是指向变量的地址,*p则访问的指向变量的值

intmain(int argc, char **argv)

{

    int*p;

 

    printf("p: address =0x%x\n", (unsigned)p);

 

    p = (int*)malloc(16);

    printf("after alloc, p:address = 0x%x\n", (unsigned)p);

    printf("(p + 1) =0x%x, (p + 2) = 0x%x\n", (unsigned)(p + 1),

                                                                                     (unsigned)(p + 2));

    free(p);

 

    return 0;

}

输出结果:

p: address = 0xc34ff4

after alloc, p: address = 0x9016008

(p + 1) = 0x901600c, (p + 2) = 0x901600a

在这个例子中需要注意一下几点:

  1. 指针p定义后是被放在栈中的,并初值为0
  2. malloc后从虚拟地址来说,将得到一段连续的内存空间,并把首地址赋予了指针p
  3. 另一个是题外话,但觉得比较重要又容易被忽略,p++与p+n的区别:

p++:从输出结果上看,地址的加1并不意味着真的在算术值上加1,而是根据这里的指针类型加了一个int的空间(即4字节),因此当0x9016008 + 4,值为0x901600c

p+n:注意这个n是大于1的整数,此时的的确确是做了一次算术运算,即把地址看成了一个数值,然后加n,即0x9016008 + 2,值为0x901600a

因此p为一个int型地址的时候p++的结果反而大于p+2!!

 

int**p;

定义一个二维指针变量,p是指向变量的首地址也是第一维,*p是第二维,**p则访问指向变量的值

注意分配内存空间的时候是先分配行(第一维),再分配列(第二维)…...

 

  1. 数组的定义

前面已经说过指针和数据是不同的,尽管用起来很相似,而就我个人习惯而言,我不太喜欢数组中掺合指针的定义方式,因为我经常会在数据的访问是被这种玩法搞晕,例如:

int (*p)[3][2]

p到底在栈中还是堆中,p[2]又指向哪里,p[2][0]呢,*p[1][1]呢,我还需要为谁分配内存?

但这种定义从原理上来说却很简单,无非是定义了一个3*2的二维数组,数组总的每个元素都是一个指针变量,指向不同的地址。但在实际开发中还要考虑到各种指针的内存管理问题,而访问时又是通过数组的形式去访问,只要稍有不慎就“段错误”。所以我更喜欢把*p[3][2]定义为***pp[3][2][]的形式,即要么全部由我自己管理内存,要么就全部由系统帮我分配(视情况而定)。当既然要深入学习指针,这种情况还是不得不讨论滴!

:)

 

指针数组与数组指针

在指针的定义中括号往往容易被忽略,而恰恰小括号()的优先级最高,而中括号[]的优先级又大于星号*,你定义的是指针还是数组就取决于这个不起眼的小括号

int *p[];

定义一个指针数组,该数组的每个元素都是一个指向变量地址的指针,注意归根结底这是一个数组

int main(int argc, char **argv)

{

    char str[2][6] = {"hello", "world"};

    char *p[2];    //定义一个含有两个指针的数组

 

   p[0] = str[0];

   p[1] = str[1];

    printf("str:str[0] addr = 0x%x, str[1] addr = 0x%x\n",

                                (unsigned)str[0],

                                (unsigned)str[1]);

    printf("p:p[0] addr = 0x%x, p[1] addr = 0x%x\n",

                                (unsigned)p[0],

                                (unsigned)p[1]);

    printf("p[0] =%s p[1] = %s\n", p[0],p[1]);

 

    return 0;

}

输出结果:

str: str[0] addr = 0xbfb77dbc, str[1] addr =0xbfb77dc2

p: p[0] addr = 0xbfb77dbc, p[1] addr = 0xbfb77dc2

p[0] = hello p[1] = world

在上面的例子中应当注意:

  1. 指针数组的定义,这是没有括号的,char  *p[2]相当于定义了两个指针

 

int(*p)[]

定义一个数组指针,该指针指向了一个一维数组的首地址,注意归根结底这是一个指针

这算是一种比较严谨的定义方式,只是我个人不太喜欢,因为操作起来比较麻烦,呵呵

intmain(int argc, char **argv)

{

    char(*p)[2];

 

    p = "he";

    printf("p = %s\n", *p);

    printf("p[0] = %c, p[1] =%c\n", (*p)[0], (*p)[1]);

 

    return 0;

}

输出结果:

p = he

p[0] = h, p[1] = e

 

 

  1. 函数的定义

我个人认为,指针运用最多的地方还在于指针与数组和结构体之间的微妙关系,指针常与数组或结构体直接身份互换以达到数据访问的目的,而把指针运用到函数中后,难免觉得有些“花哨”,因为你完全可以运用这种方式把纯C代码封装的和C++差不多,例如:

typedef struct _People{

    unsigned char age;

    int (*run)();

}People;

 

static int people_run(void)

{

    printf("i amrunning!!\n");

    return 0;

}

 

int main(int argc, char **argv)

{

    People susam;

 

   susam.age = 20;

   susam.run = &people_run;

   susam.run();

 

    return 0;

}

这段简单的代码是可以运行的,而在结构体People中像是C++中的类,定义了一个函数指针run像是类中的方法,当调用run()方法时也确实打印了I amrunning字样,我所说的“花哨”也就在于此。当然,如果函数指针运动得当,可以把一个项目的代码封装的很好。

 

指针函数与函数指针:

与前面的数组定义一样,不要忘了小括号,否则后果自负

int*func(void);

定义一个指针函数,这个函数的返回值必须是一个指针(int *),注意这归根结底是一个函数

static int *add(int x, int y)

{

    int *ret;

   

   ret = (int *)malloc(sizeof(int));

   *ret = x + y;

   

    return ret;

}

 

int main(int argc, char **argv)

{

    int a, b;

    int *p;

 

   a = 1;

   b = 2;

   p = add(a, b);

    printf("*p =%d\n", *p);

    free(p);

 

    return 0;

}

输出结果:

*p = 3

从上面的代码中可以看出,int *add()为一个函数,而它返回值也将是一个(int *)类型的指针,p = add(a, b)这条指令并不是把add地址赋给p,而是把调用add之后的结果的地址赋给了p

 

int(*func)(void);

定义一个函数指针,这个函数的返回值是一个整数,注意这归根结底是一个指针

static int add(int x, int y)

{

    int ret;

 

   ret = x + y;

   

    return ret;

}

int main(int argc, char **argv)

{

    int a, b;

    int (*p)(int ,int);

 

   a = 1;

   b = 2;

    p = &add;

    printf("p ret= %d\n", p(a, b));

 

    return 0;

}

输出结果:

p ret = 3

尽管p被定义成了指针,但是可以看到最终打印的时候却打印的是a+b的一个整数结果,正是因为p是一个函数指针,而它的作用是指向和调用已经定义好的add函数,而不是指向某个变量。

 

一点点总结——函数指针数组

或许看名字会有点晕吧,不过先来看个例子,应该就明了了:

static int add(int x, int y)

{

    return (x + y);

}

 

static int dec(int x, int y)

{

    return (x - y);

}

 

static int mul(int x, int y)

{

    return (x * y);

}

 

int main(int argc, char **argv)

{

    int a, b;

    int (*p[3])(int ,int);

 

   a = 3;

   b = 2;

    p[0] = &add;

    p[1] = &dec;

    p[2] = &mul;

    printf("a + b= %d\na - b = %d\na * b = %d\n",

                  p[0](a, b), p[1](a, b), p[2](a,b));

    return 0;

}

输出结果:

a + b = 5

a - b = 1

a * b = 6

说明一下,上面的例子中,p是一个数组,拥有3个元素的数组,每个元素又是一个指针,指向了不同的函数。其实很简单,例子中实现了三个函数,而在main函数中定义一个p指针数组来分别指向这三个函数的地址,最后通过不同的下标调用出不同的函数,这种做法有点绕,好在上述例子还算简单,在写一些大的工程时,用这种方式确实可以把结构封装的点单易用,不过实现起来可就不容易啦。^^

 

  1. 结构体的定义

指针与结构体相结合的运用在项目开发中是最多的,因为在大的工程当中,模块与模块传递信息的时候不可能总是一个变量一个变量的传递,而是把他们都包涵到结构体中,通过地址来传递。

typedef struct _People  {

unsigned age;

char * name;

unsigned float height;

unsigned float weight;

void (*say_hello)(char *);

}*People;

 

typedef struct_Student {

People *info;

int (*study)(char *);

}*Student;

 

typedef struct _Teacher {

People *info;

int (*teach)(char *);

}*Teacher;

 

typedef struct _Class {

Student *students;

Teacher *teachers;

}*Class;

句柄:

什么是句柄,我喜欢这么理解,电脑的键鼠,汽车的方向盘...就是说某个事物的直接操作的关键部分,该部分对于整个事物来说或许很小,但却完完全全地控制住了它。而对于一个结构体来说访问它的指针变量就是句柄。

结构体的内存控制:

我认为这也是一个需要注意的地方,对于一个结构体,如何分配释放内存很重要,例如定义struct Str *p后就直接其内部元素操作的话是没有任何意义的,还需要先对这个p指针(或者叫句柄)malloc一段内存空间才行。

同样用例子来说明问题,例如一个班级,班级里有不同的老师,不同的学生,老师和学生都是人类,都有共性但也有不同的地方(继承关系?感觉在说面向对象,呵呵),不管那些,看本小结开始定义了四个结构体,_People_Student_Teacher_Class,现在就用这四个结构体分配出一个班级,班级里有不同的学生和老师,假如学生有5个,老师有2个。

问题来了,是先分配_Class,还是先分配_People,或者其他?

很简单,由外向内分配,由内向外释放

哪个是外?哪个是内?Class是外,People是内,要区分这个太容易了,看他们的包涵关系,4个结构体中,Class包涵了TeacherStudent,因此它在TeacherStudent之外,再往下看,TeacherStudent包涵了People,因此他们两在People之外,People在最里,因此如果要分配一个由5个学生和2个老师组层的班级可以按如下方式:

Class myClass;

 

myClass = (struct Class *)malloc(sizeof(struct _Class));

myClass->students = (struct _Student *)malloc(sizeof(struct _Student) *5);

myClass->teachers = (struct _Teacher *)malloc(sizeof(struct _Teacher) *2);

myClass->students->info= (struct _People *)malloc(sizeof(struct _People));

myClass->teachers->info= (struct _People *)malloc(sizeof(struct _People));

这样一个班级的内存空间就分配完毕(其实没有,要下班了,明天再解释)。


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值