倒霉的菜鸟

  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

一, 指针的基本概念

指针是一种变量类型, 其值为地址。 在32位系统中, 指针占4个字节, 64位系统中, 指针占8个字节。

声明指针时应对指针进行初始化, 不再使用时应将其置为NULL

野指针: 未初始化的指针

悬空指针: 指针最初指向的内存已经被释放

 1 void test_0() {
 2     int i = 136;
 3     //定义一个Int型指针ptr_i
 4     //它的值是i的地址
 5     int *ptr_i = &i;
 6     printf("i的地址为:%p\n", &i);//000000000061FDEC
 7     printf("ptr_i的值为:%p\n", ptr_i);//000000000061FDEC
 8     //指针也是一种变量类型, 它也有自己的地址
 9     printf("ptr_i的地址为:%p\n", &ptr_i);//000000000061FDE0
10     //在32位系统中, 指针占4个字节, 64位系统中, 指针占8个字节
11     printf("指针的大小为:%d\n", sizeof(ptr_i));//8
12     //下面这些的输出结果都为8 (64位系统)
13     printf("%d\n", sizeof(int *));//8
14     printf("%d\n", sizeof(char *));//8
15     printf("%d\n", sizeof(long long *));//8
16     printf("%d\n", sizeof(void *));//8
17     //我们可以将其他任意类型指针赋值给Void型指针, 如:
18     void *ptr_v;
19     printf("赋值之前void型指针的地址为:%p\n", &ptr_v);//000000000061FDD8
20     ptr_v = ptr_i;
21     printf("%p\n", ptr_v);//000000000061FDEC
22     //当然, 只是赋值, 它本身的地址不变
23     printf("赋值之后void型指针的地址为:%p\n", &ptr_v);//000000000061FDD8
24 }

为了更好地理解指针, 再看一个数据交换的例子

1 void swap(int x, int y) {
2     printf("交换前x=%d, y=%d\n", x, y);
3     int temp = y;
4     y = x;
5     x = temp;
6     printf("交换后x=%d, y=%d\n", x, y);
7 }


1 int main() {
2     int c = 2, d = 3;
3     printf("交换前c=%d, d=%d\n", c, d);
4     //这种方式交换并没有效果, 因为传递到方法里的是值
5     swap(c, d);
6     printf("交换后c=%d, d=%d\n", c, d);
7 
8     return 0;
9 }

上面的方法并不能达到交换的目的, 虽然swap方法完成了x和y的值交换, 但因为我们在Main方法中传递进去的是c和d的

所以交换之后, main方法中, c和d的地址上还是之前的值

代码输出结果为:

1 交换前c=2, d=3
2 交换前x=2, y=3
3 交换后x=3, y=2
4 交换后c=2, d=3

所以正确的交换姿势是使用指针

 1 //指针传递, 传递的是变量的地址
 2 //*x表示x地址上的值, 本方法内部交换的还是值
 3 void swap1(int *x, int *y) {
 4     printf("传递进来的地址为:x=%p, y=%p\n", x, y);
 5     printf("地址上的值:交换前x=%d, y=%d\n", *x, *y);
 6     int temp = *y;
 7     *y = *x;
 8     *x = temp;
 9     printf("地址上的值:交换后x=%d, y=%d\n", *x, *y);
10 }
11 
12 
13 int main() {
14 
15     //正确方式是传递地址
16     c = 20, d = 30;
17     printf("交换前c=%d, d=%d\n", c, d);
18     printf("c和d的地址为:&c=%p, &d=%p\n", &c, &d);
19     //指针传递, 传递变量的地址
20     swap1(&c, &d);
21     printf("交换后c=%d, d=%d\n", c, d);
22     return 0;
23 }

swap1方法内部交换的还是值, 但是我们在Main方法中调用的时候, 传递进来的是变量c和d的地址

然后在swap方法中根据地址取值进行交换, 所以运行后c和d的地址不变, 但地址上的值发生了交换:

1 交换前c=20, d=30
2 c和d的地址为:&c=000000000061FE1C, &d=000000000061FE18
3 传递进来的地址为:x=000000000061FE1C, y=000000000061FE18
4 地址上的值:交换前x=20, y=30
5 地址上的值:交换后x=30, y=20
6 交换后c=30, d=20

 

二, 指针的基本操作

& 运算符, 取地址

* 解引用, 也叫间接寻址符。 *p得到的是指针p所指向的地址上的值

& 和 *是一对互逆运算

 1 void test_2() {
 2     //定义int型变量a,值为10
 3     int a=10;
 4     //定义int型指针p, 其值为变量a的地址
 5     int *p = &a;
 6     //*p表示指针p所指向的地址上的值, 也就是解引用
 7     int pv = *p;
 8     printf("a=%d\n", a);//10
 9     printf("pv=%d\n", pv);//10
10     //给解引用赋值,就是给指针p指向的地址上的内存赋值
11     *p=100;
12     printf("a=%d\n", a);//100
13 }

指针的算术运算: 对于变量来说,不同的类型占用的空间大小是不同的;但是不同类型的指针,占用的空间都是相同的(32位系统中占4个字节,64位则占8个字节)。既然占用的空间一样, 为什么还要把指针分为各种类型呢? 就是为了指针的算术运算。  指针只能进行加减法运算, 包括 +, -, ++, --, +=, -=

1 int i = 100;
2     //定义Int型指针ptr, 其值为i的地址
3     int *ptr = &i;
4     printf("%p\n", ptr);//000000000061FDE4
5     //int型占4个字节
6     printf("%p\n", ptr+1);//000000000061FDE8
7     printf("%p\n", ptr+2);//000000000061FDEC

s上面代码中, ptr是一个Int型指针, 所以ptr+1就会移动4个字节

1 char c = 100;
2     //定义char型指针ptr, 其值为c的地址
3     char *ptr_c = &c;
4     printf("%p\n", ptr_c);//000000000061FDDB
5     //char型占1个字节
6     printf("%p\n", ptr_c-1);//000000000061FDDA
7     printf("%p\n", ptr_c+1);//000000000061FDDC
8     printf("%p\n", ptr_c+2);//000000000061FDDD

ptr_c是一个char型指针, 所以每次就只移动一个字节

因此, 我们说, 指针移动的步长与其所指类型相关

指针与一维数组:

在c语言中, 我们通常这样来定义一个Int型数组:

int array[] = {11,22,33,44,55};

注意: 数组名array, 可以代表数组的起始地址, 也是数组第一个元素的地址, 也是一个常量指针

1    int array[] = {11,22,33,44,55};
2     printf("array=%p\n", array);//000000000061FDD0
3     int *ptr_array = array; //注意这里不需要 = &array, 因为数组名就是一个指针
4     printf("ptr_array=%p\n", ptr_array);//000000000061FDD0
5 
6     printf("&array%p\n", &array);//000000000061FDD0, 数组名就是数组的起始地址
7     printf("&array[0]=%p\n", &array[0]);//000000000061FDD0, 也是第一个元素的地址

上面代码中, array, &array, &array[0]的地址都是相同的

我们可以通过 array[2]=100; 来将数组中第三个元素的值修改为100

同样也可以通过指针来进行操作

1     ptr_array[2] = 50;
2     printf("array[2]= %d\n", array[2]);//50

或者结合指针的算术运算

1 *(ptr_array+1) = 888;//表示指针前进一步, 然后给他上面的值赋为888
2     printf("%d\n", array[0]);//11
3     printf("%d\n", array[1]);//888
4     printf("%d\n", array[2]);//50

上面的  *(ptr_array+1) = 888; 等价于 array[1]=888; 等价于 *(array+1)=888;

 1 //小结: array+3,  &array[3], ptr_array+3, &ptr_array[3]  它们代表的是同一地址
 2     printf("=====%p\n",array+3);
 3     printf("=====%p\n",&array[3]);
 4     printf("=====%p\n",ptr_array+3);
 5     printf("=====%p\n",&ptr_array[3]);
 6     //同理, *(array+3),  *&array[3], *(ptr_array+3), *&ptr_array[3]  它们代表的是同一个值
 7     printf("=====%d\n",*(array+3));
 8     printf("=====%d\n",array[3]);
 9     printf("=====%d\n",*(ptr_array+3));
10     printf("=====%d\n",ptr_array[3]);

 

数组指针与指针数组???

1 int (*ptr)[5]        //数组指针,
2 int *ptr_array[5]       //指针数组

对于代码 int (*ptr)[5]来说, 我们首先是定义了一个指针, 指针的名字是ptr, 

                                          而int修饰的是数组的内容, 表示数组的每个元素都是Int

                                           所以, 它是一个数组指针, 指向的是  一个包含了5个Int类型的 数组 (或者说这个指针的类型是  一个包含了5个Int的数组), 数组在这里没有名字, 它是一个匿名数组。 

 

对于代码 int *ptr_array[5] 来说,  因为[]的优先级比*高, 所以首先ptr_array和[]结合, 得到一个数组, 然后 int* 表示这个数组的每个元素都是一个Int型的指针

1        int (*ptr)[5] = &array; //正确
2 //    int (*ptr_1)[5] = array;//会报错, 不能把一个Int型指针赋值给一个数组指针, 类型不同
3 //    int (*ptr_2)[4] = &array;//报错, 不能把一个类型为5个Int的数组指针  赋值给一个类型为4个int的数组指针

上面代码可以很好的证明这个问题

注意array和&array的区别, 虽然二者打印出来的地址相同,  但是array是数组第一个元素的地址, 而&array是数组的地址

 1     //定义一个数组
 2     int array[] = {11,22,32,44,51};
 3     //定义一个指针
 4     int *ptr_array = array;
 5     printf("array=%p\n", array);//000000000061FDD0
 6     printf("&array=%p\n", &array);//000000000061FDD0
 7 
 8     //步长为一个INT类型的字节数
 9     printf("array+1=%p\n", array+1);//000000000061FDD4
10     //偏移的是array[5]类型的, 数组整体作为一个偏移单元
11     printf("&array+1=%p\n", &array+1);//000000000061FDE4

因此, 上面代码中, array+1相对于array偏移了4个字节; 而&array+1则偏移了一个数组的长度-- 5个元素 * 每个元素4个字节 = 20个字节

 

数组的遍历:

1     for (int i=0; i<5; i++) {
2         printf("%d\n", array[i]);
3     }
4 
5     //指针的一个简单应用---以指针的形式遍历数组
6     int* p=NULL;
7     for(p=array; p<array+5; p++){
8         printf("地址:%p=====%d\n",p, *p);
9     }

最后看一个比较有意思的问题:

1  int aa[5] = {1,2,3,4,5};
2     //定义了一个int型指针, 它的名字叫ptr_aa, 它的值是 数组aa的地址偏移量+1
3     //数组aa占用了20个字节, 所以它的地址就是在aa地址的基础上偏移了20个字节
4     int *ptr_aa = (int*)(&aa+1);
5     //ptr_aa-1的意思就是, 在上面得到的ptr_aa的地址的基础上, 往回偏移一步
6     //我们说步长与指针类型相关, ptr_aa是一个Int型指针, 所以步长为4个字节
7     //因此*(ptr_aa-1)即ptr_aa-1这个地址上的值, 应该为5 
8     printf("===%d, %d\n", *(aa+1), *(ptr_aa-1));
posted on 2021-12-02 19:48  倒霉的菜鸟  阅读(138)  评论(0编辑  收藏  举报