一, 指针的基本概念
指针是一种变量类型, 其值为地址。 在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));