指针

指针也就是内存地址,指针变量是用来存放内存地址的变量,在同一CPU构架下,不同类型的指针变量所占用的存储单元长度是相同的,而存放数据的变量因数据的类型不同,所占用的存储空间长度也不同。有了指针以后,不仅可以对数据本身,也可以对存储数据的变量地址进行操作。

指针描述了数据在内存中的位置,标示了一个占据存储空间的实体,在这一段空间起始位置的相对距离值。在 C/C++语言中,指针一般被认为是指针变量,指针变量的内容存储的是其指向的对象的首地址,指向的对象可以是变量(指针变量也是变量),数组,函数等占据存储空间的实体。

  • 有10个指针的数组,该指针指向一个整数:int* a[10]

  • 指向有10个整型数组的指针:int (* a)[10]

  • 函数指针 是一个指向函数的指针,该函数有一个整形参数并返回一个整形数:int (*func)(int)

指针与数组

int main()
{
    int a[5] = { 1, 2, 3, 4, 5 };
    int *ptr = (int *)(&a + 1);     //&a取出整个数组的地址;&a+1跳过一个数组
    //&a的类型为:数组指针  int(*)[5] 所以要强转
    //a为数组名,首元素地址,即为1的地址,+1,跳过一个元素,即为2的地址
    printf( "%d,%d", *(a + 1), *(ptr - 1));  // 2  5
    return 0;
}
int main()
{
    int a[4] = { 1, 2, 3, 4 };
    int *ptr1 = (int *)(&a + 1);
    int *ptr2 = (int *)((int)a + 1);
    printf( "%x,%x", ptr1[-1], *ptr2);      // 4 2000000
    return 0;
}

int ptr1 = (int *)(&a + 1): 取出数组的地址+1,跳过一个数组,因为&a的类型为数组指针:int()[4] 类型不匹配,所以强转为int类型;

  • prt1[-1]==> *(ptr1+(-1))==>*(ptr1-1)

  • int *ptr2 = (int *)((int)a + 1)

此时的a代表的首元素地址,地址值是一个常量,整数+1:相当于跳过一个字节,注意要考虑小端存放,读取时倒着读取的问题,所以ptr2指向的是00 00 00 02这四个字节,所以打印结果为:02000000

int main()
{
    int a[5][5];
    int(*p)[4];     //p是数组指针,指向的数组有4个元素
    p = a;
    printf( "%p,%d\n", &p[4][2]-&a[4][2], &p[4][2]-&a[4][2]);   //指针-指针得到的是二者之间的元素个数
    return 0;
}
  • p[4] = *(p+4)

  • p[4][2] ==> *(*(p+4)+2)

  • &p[4][2]为小地址,&a[4][2]为大地址,小地址减大地址,所以最后结果为-4

警告

a是二维数组,对应数组指针的类型为:int(*)[5],指向的是有5个元素的一维数组,而p是数组指针,指向的数组只有4个元素,所以会有警告,可以写成 int(p)[4] = (int()[4])a 消除警告

int main()
{
    int a[3][2] = { (0, 1), (2, 3), (4, 5) };   //逗号表达式-结果为最后一个表达式的结果,所以只是初始化了{ 1, 3, 5 }
    int *p;
    p = a[0]; //a[0] : 二维数组第一行的数组名,在这里是首元素地址,即第一行第一个元素的地址
    printf("%d ",p[0]);     //1
    return 0;
}
int main()
{
    int aa[2][5] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
    int *ptr1 = (int *)(&aa + 1);
    int *ptr2 = (int *)(*(aa + 1));
    printf( "%d,%d", *(ptr1 - 1), *(ptr2 - 1));    // 5 10
    return 0;
}
  • &aa:取出二维数组的地址;&aa+1:跳过二维数组,&二维数组应该使用数组指针接收,现在保存到整形指针,所以要强转。

  • aa:没有单独放在sizeof内部,没有&数组名,所以代表的是二维数组首元素地址,即二维数组第一行的地址;aa+1:跳过一行

  • *(aa+1) : 相当于拿到了第二行的数组名,等价于 aa[1]

指针与结构体

提示

指针+1 的步长取决于指针指向的数据的类型,整数+1 ->跳过一个字节,执行普通的加减运算,而整形指针+1 -> 跳过四个字节

struct Test
{
    int Num;
    char *pcName;
    short sDate;
    char cha[2];
    short sBa[4];
}*p; //这里告知结构体的大小是20个字节,假设p的值为0x100000。
int main()
{
    p = 0x00100000;
    //0x1-->对应的值就是1  相当于0x00000001
    printf("%p\n", p + 0x1);//p为结构体指针,指向一个结构体,+1,跳过一个结构体,即跳过20个字节,
    //   0x00100000+20 -> 0x00100020 错误,  要将20转化为16进制再加,或者将16进制0x00100000转化为10进制之后加上20,然后再转化为16进制
    // 20-> 0X00000014
    //所以最终结果为:0x00100014
    printf("%p\n", (unsigned long)p + 0x1);//将p转化为长整形,+1,即为整形+1,  例如:500+1= 501    //所以结果为: 0x00100001
    printf("%p\n", (unsigned int*)p + 0x1);
    //将p强转为无符号整形,+1跳过一个整形->跳过4个字节
    //所以结果为:0x00100004
    return 0;
}

指针与字符

#include <stdio.h>
int main()
{
    char *a[] = {"work","at","alibaba"}; //a是数组,元素类型为:char* ,存放指向字符串首字符地址,根据后面初始化内容确定数组的大小
    char**pa = a;  //char**pa :一颗*说明pa是指针,另一颗*说明pa指向的类型是char*
    pa++;  //pa+1:跳过char*
    printf("%s\n", *pa); //打印结果为:at
    return 0;
}
int main()
{
    char *c[] = {"ENTER","NEW","POINT","FIRST"};
    char**cp[] = {c+3,c+2,c+1,c};
    char***cpp = cp;
    printf("%s\n", **++cpp);  //cpp先自增,此时cpp存放了指向存放c+2地址的空间(地址),打印结果为:POINT
    printf("%s\n", *--*++cpp+3); //*++cpp拿到存放c+1地址的空间,再--自减c+1的地址,把存放c+1的地址变成存放c的地址,"ENTER"首字符后+3打印:ER
    printf("%s\n", *cpp[-2]+3); //打印结果为:ST
    printf("%s\n", cpp[-1][-1]+1);//打印结果为:EW
    return 0;
}
  • cpp-2:从指向存放c地址空间又变为了指向存放c+3地址的空间

  • *(cpp-2):得到cpp现在指向的内容,即c+3的地址

  • **(cpp-2):得到c+3空间的内容(首字符F的地址)

  • **(cpp-2)+3 :从首字符F的地址向后+3,即为S的地址

cpp[-1] ==>*(cpp-1); cpp[-1][-1] ==> *(*(cpp-1)-1); cpp[-1][-1] +1 ==> *(*(cpp-1)-1) +1

此时的cpp指向为第二条表达式之后的状态,cpp存放c的地址,cpp-1指向存放c+2地址的空间,*(cpp-1)-1自减变成了c+1的地址,即得到了c+1的地址,再+1字符输出。