<<C语言深度剖析>>学习笔记之五:指针与数组

来源:互联网 发布:广州尔玛网络 编辑:程序博客网 时间:2024/05/21 12:41

 1.指针:

    通俗说法:

    一个基本的数据类型(如char、int、struct)后面加上"*"号就构成了一个指针类型的模子.这个模子是一定的,与"*"号前面的

数据类型无关--在32位系统下是4Byte.也可以理解成PC指针的寻址能力,比如一个32位的SOC,其寻址能力为4G(2^32).如果

不是4Byte(32bit),又怎么可以完全寻址呢?"*"前面的数据类型影响指针的步伐,比如指针加1减1其跨越内存的幅度是以这个

数据类型为单位的.


    1-1.将指定值写入到指定内存地址.

    指针可以理解成和int一样是一种"类型".我们在编程中常常会用到"强制类型转换".比如把一个int型强制转换为char型,如下:

    char cTestVal = (char)iTestVal;

    因此,我们把一个数值也可以强制转换为地址.如:

    0x12ff7c是一个数据.变为一个地址如下:

    (int *)0x12ff7c.

    往内存0x12ff7c地址上写入数值0x100.实现代码如下:

    int *p = (int *)0x12ff7c;

    *p = 0x100;

     也可以直接写成这样:

    *(int *)0x12ff7c = 0x100;


2.数组

    2-1.a,&a[0]和&a区别:

        &a[0]:数组首元素的地址;

        &a:数组的首地址.

        a和&a[0]是一样的.

        二者虽然一样,但是他们的步进值是不一样的.比如&a[0]+1和&a+1.

    示例如下:

#include <stdio.h>int main(int argc,char **argv){    int a[5]={1,2,3,4,5};    int *ptr1=(int *)(&a+1);    printf("%x,%x,%x,%x\n",*(ptr1 - 1),*(a + 1),*(int *)((int)a + 1),*(&a[0] + 1));    return 0;}

输出结果:

root@seven-laptop:~/learn/C_Program# ./array 5,2,2000000,2root@seven-laptop:~/learn/C_Program# 

对输出结果简析:

&a表示整个数组的起始地址,&a+1表示步进了sizeof(a)的地址空间大小.因此,此时ptrl是指向了数组a末尾再下一个元素的位置.ptrl - 1,指针步进是以其指向类型

为步进值的.ptrl指向int类型.因此ptrl-1表示ptrl再往回退4个byte,落在a的末端最后一个元素位置,输出5.

a和&a[0]都表示数组首元素的地址,a+1是指偏离首元素地址的下一个元素;

*(int *)((int)a + 1):(int)a + 1意思是指元素a[0]的第二个字节.由于是%x格式输出,即从这个地址开始后连续4个字节.下面画出数据在内存存放的示意图:

大端模式:

00
010002小端模式:

100
02000

[注]:上述两个表左边表示数组起始位置,地址从左往右越来越高.


小端模式:一个字的高地址存放一个字的高字节.因此,"0 0 0 2"按小端模式规则存放为"2 0 0 0".输出为0x2000000;

大端模式:一个字的高地址存放一个字的低字节.因此,"0 0 1 0"按大端模式规则存放为"0 0 1 0".输出为0x100.


    2-2.数组名作为右值:

        当数组名作为右值时,其意义和&a[0]是一样的,代表的是数组首元素的首地址,而不是数组的首地址.如:

        int array[10];

        int *p = array;

    2-3.数组名不能作为左值:

       

3.指针与数组的关系:

    3-1.相同点

        以下标的形式访问没有本质的区别.如下面这个示例:

#include <stdio.h>#include <stdlib.h>#include <string.h>int main(int argc,char **argv){    int lenp = 0,lena = 0,i = 0;    char *pCh = "0123456789";    char array[] = "0123456789";    lenp = strlen(pCh);    lena = sizeof(array);    printf("lenp = %d,lena = %d\n",lenp,lena);    for(i = 0; i < lenp; ++i)    {        printf("pCh[%d] = %c,array[%d] = %c\n",i,pCh[i],i,array[i]);    }    return 0;}
输出结果:

root@seven-laptop:~/learn/C_Program# ./point lenp = 10,lena = 11pCh[0] = 0,array[0] = 0pCh[1] = 1,array[1] = 1pCh[2] = 2,array[2] = 2pCh[3] = 3,array[3] = 3pCh[4] = 4,array[4] = 4pCh[5] = 5,array[5] = 5pCh[6] = 6,array[6] = 6pCh[7] = 7,array[7] = 7pCh[8] = 8,array[8] = 8pCh[9] = 9,array[9] = 9root@seven-laptop:~/learn/C_Program# vim point.c
      

    3-2.不同点

        3-2-1:大小

            指针:在32位系统下,永远只占4个Byte,可以指向任何地方,但是并不是每个地方都可以通过这个指针访问的;

            数组:大小与元素的类型和个数有关,可以存放任何类型的数据,但是不能存放函数.


        3-2-2:数据的访问方式

            指针:间接访问,首先取得指针变量p的内容,把它当作地址,然后从这个地址提取数据或向这个地址写入数据;

            数组:直接访问数组,只能通过"基地址+偏移量"来实现访问.


        3-2-3:处理对象

            指针:动态数据;

            数组:存储固定数目且数据类型相同的元素.


4.指针数组和数组指针

    指针数组:首先它是一个数组,数组的元素都是指针;

    数组指针:首先它是一个指针,它指向一个数组.

    下面示例对比两者的区别:

int *p1[10];指针数组,"[]"的优先级比“*”要高.因此p1先跟"[]"结合,形成数组,数组名为p1.数组元素类型为int型指针.int (*p2)[10];数组指针,p2是一个指针,它指向一个包含10个int类型数据的数组.注意这个数组是匿名的.


下面给出一个示例显示数组指针的访问数组元素的方法:

#include <stdlib.h>#include <stdio.h>#include <string.h>int main(int argc,char **argv){        int array_a[5] = {0,1,2,3,4};        int(*pa)[5];        pa = &array_a;        printf("pa[0][0] = %d\n",pa[0][0]);        printf("pa[0][1] = %d\n",pa[0][1]);        printf("pa[0][2] = %d\n",pa[0][2]);        printf("pa[0][3] = %d\n",pa[0][3]);        printf("pa[0][4] = %d\n",pa[0][4]);        printf("Address of pa = %p\n",pa);        printf("Address of (pa + 1) = %p\n",(pa + 1));        return 0;}
输出结果:

pa[0][0] = 0pa[0][1] = 1pa[0][2] = 2pa[0][3] = 3pa[0][4] = 4Address of pa = 0xbfd6856cAddress of (pa + 1) = 0xbfd68580


5.二维数组

    二维数组可以理解为一维数组,只不过这个"一维数组"里面的元素也是一个数组.

    5-1.二维数组元素的访问方式:

        1).a[i][j];

        2).*(*(a + i) + j)

        *(*(a + i) + j)可以拆开下面几步理解:

            1).a:和一维数组一样,是这个数组的首元素的首地址;

            2).a + i:和一维数组一样,表示步进"i"个数组元素,只不过这里步进的元素是"数组",因此,a + i表示第i-1个元素的地址.因为这个元素是数组.因此,a + i表示的意义变成了这个一维数组整个数组的首地址.因此,(a + i)和*(a + i)的地址值是一样的.这就相当于一维数组里面a和&a[0]的区别.只不过一个是数组的起始地址,一个是数组第一个元素的起始地址;

            3).虽然(a + i)和*(a + i)的地址值是一样的,但是它们有着本质的区别--它们的步进值是不一样的.这就相当于&a和&a[0]二者的区别.我们现在要找的是元素.因此必须以&a[0]的方式步进--即*(a + i);

            4).*(a + i) + j:确定我们要找的元素的内存地址;

            5).*(*(a + i) + j):把我们要找的元素的内存地址上的数值给读出来.

示例:

#include <stdio.h>int main(int argc,char **argv){    int array[3][4] = {{0,1,2,3},{4,5,6,7},{8,9,10,11}};    printf("Size Of Two_Array Is %d\n",sizeof(array));    printf("Size Of Two_Array[0] Is %d\n",sizeof(array[0]));    printf("Size Of Two_Array[1] Is %d\n",sizeof(array[1]));    printf("Size Of Two_Array[2] Is %d\n",sizeof(array[2]));    printf("Address of array[1][2] = %p,Address of (*(array + 1) + 2) = %p\n",&array[1][2],*(array + 1) + 2);    printf("Address of array[0] = %p,address of (array[0] + 1) = %p\n",array[0],(array[0 + 1]));    printf("array = %p,(array + 1) = %p,*(array + 1) = %p,**(array + 1) = %d\n",array,(array + 1),*(array + 1),**(array + 1));    printf("array[1][2] = %d\n",array[1][2]);    printf("*((*(array + 1)) + 2) = %d\n",*((*(array + 1)) + 2));    return 0;}
输出结果:

root@seven-laptop:~/learn/C_Program# ./twoarray Size Of Two_Array Is 48Size Of Two_Array[0] Is 16Size Of Two_Array[1] Is 16Size Of Two_Array[2] Is 16Address of array[1][2] = 0xbfd4bd4c,Address of (*(array + 1) + 2) = 0xbfd4bd4cAddress of array[0] = 0xbfd4bd34,address of (array[0] + 1) = 0xbfd4bd44array = 0xbfd4bd34,(array + 1) = 0xbfd4bd44,*(array + 1) = 0xbfd4bd44,**(array + 1) = 4array[1][2] = 6*((*(array + 1)) + 2) = 6root@seven-laptop:~/learn/C_Program# 

   

    5-2.二维数组初始化误区:

    正确写法:

    int array[2][3] = {{1,2,3},{4,5,6}};

    手误写法:

    int array[2][3] = {(1,2,3),(4,5,6)};


6.二级指针

    二级指针是指其指向的对象不是数据,而是一级指针的地址.

     

7.数组参数与指针参数

    7-1.一维数组参数

    一维数组做参数时,编译器总是把它解析成一个指向其首元素首地址的指针.下面给出一个示例:

#include <stdio.h>void func_array(int a[5]){    int *p = a;    printf("size of a = %d\n",sizeof(a));    printf("a[3] = %d\n",a[3]);    printf("p + 3 = %d\n",*(p + 3));    *(p + 3) = 8;}int main(int argc,char **argv){    int array[5] = {1,2,3,4,5};    func_array(array);    printf("array[3] = %d\n",array[3]);    return 0;}
输出结果:

root@seven-laptop:~/learn/C_Program# ./parameter size of a = 4a[3] = 4p + 3 = 4array[3] = 8root@seven-laptop:~/learn/C_Program# vim parameter.c
由于一维数组作参数时,编译器只是把这个数组的首地址传进去.因此,上述函数func_array(int a[5])把参数中的"5"去掉也是可以的.这样至少不会让人误会成只能传递一个5个元素的数组.


    7-2.一级指针参数

    传参数的时候如果涉及到传入参数的改变的话,一定要注意编译器有个"拷贝副本"的动作.

    如下,我们写一个函数来分配内存.下面的代码是有问题的:

#include <stdio.h>#include <stdlib.h>#include <string.h>void get_mem(char *p,int num){    p = (char *)malloc(num * sizeof(char));}int main(int argc,char **argv){    char *str = NULL;    get_mem(str,10);    strcpy(str,"hello");    printf("str = %s\n",str);    free(str);    return 0;}
运行直接报段错误.

    代码修改一:

#include <stdio.h>#include <stdlib.h>#include <string.h>void get_mem(char **p,int num){    *p = (char *)malloc(num * sizeof(char));}int main(int argc,char **argv){    char *str = NULL;    get_mem(&str,10);    strcpy(str,"hello");    printf("str = %s\n",str);    free(str);    return 0;}


    代码修改二:(这种方式更容易理解)

#include <stdio.h>#include <stdlib.h>#include <string.h>char *get_mem(char *p,int num){    p = (char *)malloc(num * sizeof(char));}int main(int argc,char **argv){    char *str = NULL;    str = get_mem(str,10);    strcpy(str,"hello");    printf("str = %s\n",str);    free(str);    return 0;}

8.函数指针

    函数指针,是一个指针,指向了函数.如下:

    char * (*fun1)(char *p1,char *p2);

    fun1是一个指针,它指向的对象是一个函数,这个函数的返回值是char 指针,有两个参数,均为char指针.

 

  8-1.函数指针的定义:

     char * (*fun1)(char *p1,char *p2)


    8-2.函数指针的使用:

    如同使用指针指向的变量一样,我们可以类比下面指针和函数指针:

    提取char型指针里面的内容:

    char i = *p;

    运行函数指针指向的函数:

    (*fun1)("aa","bb");

    [注:](*fun1)("aa","bb");可以按照下面理解:

    fun1为某个函数的地址,*fun1把这个函数给拿出来.其实和我们常用的函数调用是一样的:func();

    *fun1对应了这个函数名(函数名就是函数的地址标志),可以理解成一种替换:用*fun1替换func.

    示例一:

#include <stdlib.h>#include <stdio.h>#include <string.h>char *func1(char *p1,char *p2){    int i = -1;    i = strcmp(p1,p2);    if(0 == i)    {        printf("%s\n",p1);    }    else    {        printf("%s\n",p2);    }}int main(int argc,char **argv){    char *(*pf)(char *,char *);    pf = &func1;    *pf("aa","bb");    return 0;}
[注:]为了突现函数指针,上述代码中
*pf("aa","bb");
最好改写成:

(*pf)("aa","bb");

    示例二:

    写一程序让其从0地址开始运行.

    1).将0地址强制转换为函数指针:

        (void(*)())0

    2).函数指针所指向的内容和我们常用的函数调用只是一种替换关系(分析见上述):

        (*(void(*)())0)();

   

9.函数指针数组

    函数指针数组,分开理解:首先它是一个数组;其次,数组里面的元素有是类型的,那么元素类型就是函数指针.如:

    char *(*pf[3])(char *p);

    分析:

        1).*pf[3],"[]"的优先级比"*"高,因此,它是一个数组,数组名是pf,里面有3个元素;

        2).去掉数组名、"[]"、元素个数"3",剩下的就元素类型了.如下:

        char *(*)(char *p),这是一个函数指针,指向的函数类型为:返回值为char *,只有一个参数,是char *.

    下面给出一个示例:

#include <stdio.h>#include <stdlib.h>char *func1(char *p){    printf("%s\n",p);    return p;}char *func2(char *p){    printf("%s\n",p);    return p;}char *func3(char *p){    printf("%s\n",p);    return p;}int main(int argc,char **argv){    char *(*pf[3])(char *);    pf[0] = func1;    pf[1] = func2;    pf[2] = func3;    pf[0]("func1");    pf[1]("func2");    pf[2]("func3");    return 0;}
输出结果:

root@seven-laptop:~/learn/C_Program# ./pffunc1func2func3root@seven-laptop:~/learn/C_Program# vim pf.c 
总结分析:

    函数指针:

        1).首先它是一个指针,而指针指向是有类型的,比如int;

        2).其次这个指针安放的内容是这个函数的地址.我们提取一个普通的指针只要在指针前面加"*",函数指针其实也一样,只不过为了突出它是函数指针,用"()"括起来.如下:

            普通指针:

            int *p;

            int iTestVal = *p;

            函数指针:

            int *(*pf)(int i);

            int *func(int i);

            pf = &func;    //这里func和&func实际效果是一样的,只不过本质是完全不同而已.类似一维数组&a和&a[0].

            (*pf)(5);      //实际使用函数指针只需要把函数指针的内容提取出来(*pf)替换函数名我们常规调用方法一样.这里只是用(*pf)替换func.


    函数指针数组:

        1).首先它是一个数组,数组是有数组名、元素个数、元素类型.因此,我们定义一个函数指针数组可以按下面的流程走:

        确定数组名及元素个数-->确定数组里面的元素类型.如下:

        pf[3] -- > char *(*pf[3])(char *)

        2).函数指针数组里面的元素都是函数指针,如pf[0]、pf[1]、pf[2]类型都是char *(*)(char *);

        3).只要函数形式是:返回值为char *、参数只有一个而且是char *的都可以被安放在pf[0]、pf[1]、pf[2];

        4).实际使用过程的时候,和常规调用方式一样,只是把函数指针安放的内容去替换函数名.

   

10.函数指针数组的指针

        char *(*(*pf)[3])(char *);

        上面就是一个函数指针数组的指针.下面我们分步来理解:

        1).首先它是一个指针:(*pf);

        2).既然是一个指针,它总会有指向对象.从大体上看这个指针是指向一个数组(*pf)[3].这个数组有三个元素,每个元素的类型是char *(*)(char *);


       函数指针数组的指针, 难理解,很少用.不说了.买了个表.

       



原创粉丝点击