个人技术生活分享

文明其精神,野蛮其体魄

0%

理解指针

一、引言:

日常开发中,我们经常遇到指针,同时指针也是C语言里极为重要的一个知识点,今天我们就来把它彻底弄明白。

二:正文

变量与变量值:

1
2
//数据类型(int) 变量名(a) = 值(10)
int a = 10;

变量:变量是一个抽象概念,可以理解为允许存放数据的空间。当声明一个变量,计算机就会分配空间存放数据。变量可以通过变量名访问。

变量值:变量里面存储的数据即为变量值,变量值可以为数值、字符等类型。

指针:

指针:是一种变量,它的值是一个内存地址。(从三个方面分析指针:指针的类型、指针所指向的类型、指针的值)

指针类型:数据类型 *,int *:整形数据类型的指针; char *: 字符数据类型的指针; void *:无数据类型的指针;等等…

取地址运算:&变量 ,取得的结果是变量的地址,是一个(eg:x10db5640)。

取变量运算:*指针变量,取得的结果是一个变量。

1.整形数据类型指针:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
- (void)testIntExample {

//int类型变量a赋值为10
int a = 10;
//int*类型的指针变量b赋值为&a
int *b = &a;
//int类型变量c赋值为*b
int c = *b;

printf("变量b---值:%d 地址:%p",*b,b);
printf("变量c---值:%d 地址:%p",c,&c);

打印结果:
变量b---值:10 地址:0x10db5641c
变量c---值:10 地址:0x10db5640c
}

解读:指针变量b的类型是:int *,指针变量b所指向的变量类型是int类型,b的值是一个地址:0x10db5641c。

2.字符数据类型指针:

1
2
3
4
5
6
7
8
9
10
11
12
13
- (void)testCharExample {

char *a = "blog";
char *b = a;
char *c = b;

printf("变量b所指变量的值:%s 变量b所指变量的首地址:%p 变量b的地址:%p",b,b,&b);
printf("变量c所指变量的值:%s 变量c所指变量的首地址:%p 变量c的地址:%p",c,c,&c);

打印结果:
变量b所指变量的值:blog 变量b所指变量的首地址:0x10a951bfc 变量b的地址:0x10a76f410
变量c所指变量的值:blog 变量c所指变量的首地址:0x10a951bfc 变量c的地址:0x10a76f408
}

解读:char*类型指针 变量a 指向char类型变量blog,a的值是 变量blog的首地址0x10a951bfc。

3.指针与数组:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
- (void)testArrayExample {

int arr[] = {4,12,34,6,7,788};
int *a = arr;//得到的是arr的首地址
int *b = &arr[0];//得到的是arr数组第一个元素的地址
int *c = arr + 1;//得到的是arr数组第二个元素的地址
int *d = &arr[0] + 1;//得到的是arr数组第二个元素的地址

printf("a:%p---b:%p",a,b);//可以得出a和b是相等的
printf("c:%p---d:%p",c,d);//可以得出c和d是相等的
printf("num:%ld",c-a);//可以得出c和a之间有几个元素
printf("数组中第一个元素的字节数:%ld",sizeof(arr[0]));//获取数组第一个元素的字节数

*c = 99;
printf("arr+1: %d---%p",*c,c);

//打印结果:
a:0x106f54410---b:0x106f54410
c:0x106f54414---d:0x106f54414
num:1
数组中第一个元素的字节数:4
arr+1: 99---0x106f54414
}

解读:

1)arr 表示arr[]数组的首地址

2)指针c - 指针a,能获取到两个指针之间有几个元素

3)指针变量加减一个整数,p(指针)(+/-)n(整数)计算方式为:指针地址 (+/-) n * 变量占有的字节数

4)sizeof的用法:

  • 每种类型的数据在内存单元中占有的字节数是固定的,因此sizeof(arr)/sizeof(arr[0]) = 数组的元素的个数
  • sizeof(arr):获取的是数组总共占有多少字节
  • sizeof(arr[0]):获取的是数组中一个元素占有的字节数

5)数组变量保存的是地址,但数组变量中保存的地址是不能改变的,注意说的是指针地址不能变,但是指针指向的值是可以变的,因此数组变量应称为指针常量,可以改变数组中地址中存储的值。

OC 应用场景示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//采用这种方式传参,可以获取到error的值
- (void)testErrorExample {

NSError *err = nil;
[self handleResponseCode:400 error:&err];

NSLog(@"err---%p--%@",err,err);

//指针运算符&和*是从右往左运算,他们的优先级相同。*&err和err一样。
NSLog(@"*&err---%p--%@",*&err,*&err);

//打印结果
err---0x6000031d07e0--Error Domain=NSCocoaErrorDomain Code=100 "(null)" UserInfo={data=99}
*&err---0x6000031d07e0--Error Domain=NSCocoaErrorDomain Code=100 "(null)" UserInfo={data=99}

}

- (void)handleResponseCode:(NSInteger)code error:(NSError **)error{
if (code != 0) {
*error = [NSError errorWithDomain:NSCocoaErrorDomain code:100 userInfo:@{@"data":@"99"}];
}
}

三、扩展

  1. 操作系统分为32位和64位,32位的最多只能支持4GB的内存,因为计算机底层并不使用十进制,使用的是二进制,32位的二进制只能支持4G个编号,这意味着操作系统只能为4GB的内存单元编号,因此32位的操作系统最多只能支持4GB的内存,多余的内存不会有编号,因此无法将数据存入这些内存单元中。

  2. 指针存储于栈区,不同数据类型的指针大小都相等,因为他们的值都是一个地址,并且长度也相同。32位操作系统中,指针大小是4个字节,64位操作系统中指针大小是8个字节。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    //下面代码是在64位操作系统演示
    -(void)testGetPointerNum {

    char *a = "myBlog";

    int num1 = 1234325;
    int *b = &num1;

    double num2 = 3232.434;
    double *c = &num2;

    printf("char *类型指针变量a所占字节数:%ld",sizeof(a));
    printf("int *类型指针变量b所占字节数:%ld",sizeof(b));
    printf("double *类型指针变量c所占字节数:%ld",sizeof(c));

    //打印结果
    char *类型指针变量a所占字节数:8
    int *类型指针变量b所占字节数:8
    double *类型指针变量c所占字节数:8
    }
  3. 结构体数据类型变量的大小:对于某个结构体类型而言,其存储单元大小,等于它当中占用空间最大的基础类型所占用的字节数量。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    - (void)testStructExample {

    struct Data1{
    int a;
    char b;
    char c;
    };

    struct Data2{
    char c;
    int a;
    char b;
    };

    struct Data1 p1;
    p1.a = 1;
    p1.b = 'd';
    p1.c = 'f';

    struct Data2 p2;
    p2.a = 1;
    p2.b = 'd';
    p2.c = 'f';

    printf("p1结构体占用的字节个数为:%ld",sizeof(p1));
    printf("p2结构体占用的字节个数为:%ld",sizeof(p2));

    //打印结果
    p1结构体占用的字节个数为:8
    p2结构体占用的字节个数为:12
    }

    解读:

    结构体内存结构示意图

    你可以看到,在 Data1 中,首先是 int 类型的 a 变量,占用了第一个存储单元,然后 b 和 c 占用了第二个存储单元的前两个字节。再看 Data2,由于 Data2 不同于 Data1 的字段顺序,b 占用了第一个存储单元的第一个字节,剩余的 3 个字节不够存放一个 int 类型变量的,所以按照上面我们讲的规则“当本存储单元不够安放的时候,就从下个存储单元的头部开始安放”, a 变量就单独占用了第二个存储单元,c 自己占用第三个存储单元的第一个字节。所以,虽然在数据表示上,Data1 和 Data2 是等价的,可 Data2 却占用了更多的存储空间,相比于 Data1 造成了 50% 的空间浪费。由此可见,在设计结构体的时候,不仅要设计新的结构体类型中所包含的数据字段,还需要关注各个字段之间的顺序排布。

  4. 32位操作系统内存与地址分布图:

内存空间布局

  1. 64位操作系统常用数据类型占用的内存大小:

    常用数据类型 占用内存大小(字节)
    char、unsigned char 1
    bool 1
    short、unsigned short 2
    int、unsigned int 4
    long、unsigned long 8
    float 4
    double 8
    指针 8