对于指针的基本了解
低阶指针
文章目录
- 低阶指针
- 1.理解指针
- 2.指针与指针类型
- 3.野指针
- 4.指针运算
- 5.指针与数组
- 6.二级指针
- 7.指针数组
1.理解指针
指针的理解:
指针就是地址,平时说的指针是指针变量,是用来存放内存地址的变量指针在内存中的存储:
我们可以通过==&==(取地址操作符)取出变量的内存起始地址,把地址可以存放到一个变量中,这个变量就是指针变量,比如上图的p
#define _CRT_SECURE_NO_WARNINGS #include <stdio.h> int main() { int a = 10;//在内存中开辟一块空间 int *p = &a;//这里我们对变量a,取出它的地址,可以使用&操作符 //a变量占用4个字节的空间,这里是将a的4个字节的第一个字节的地址存放在p变量中,p就是一个指针变量 return 0; }
调节内存监视窗口显示规格为4个位,则2个数表示一个字节,int是4个字节,则0a表示的是10,存储了a=10
指针的大小:
补充:
对于32位的机器,假设有32根地址线,那么假设每根地址线在寻址的时候产生高电平(高电压)和低电 平(低电压)就是(1或者0);这里就有2的32次方个地址,每个地址标识一个字节,那我们就可以给 (2^32Byte == 2^32/1024KB == 2^32/1024/1024MB==2^32/1024/1024/1024GB == 4GB) ,则有4G的空闲进行编址
结论:在32位的机器上,地址是32个0或者1组成二进制序列,那地址就得用4个字节的空间来存储,所以一个指针变量的大小就应该是4个字节
结论:在64位机器上,有64个地址线,那一个指针变量的大小是8个字节,才能存放一个地址
==结论:==指针的大小只与地址线个数有关
2.指针与指针类型
指针类型:
char *pc = NULL; short *ps = NULL; int *pi = NULL; char* 类型的指针是为了存放 char 类型变量的地址 short* 类型的指针是为了存放 short 类型变量的地址 int* 类型的指针是为了存放 int 类型变量的地址
指针类型的含义:指针的类型决定了指针向前或者向后走一步有多大(距离)
#define _CRT_SECURE_NO_WARNINGS #include <stdio.h> int main() { int n = 10; char* pc = (char*)&n; int* pi = &n; printf("&n=%p\n", &n); printf("pc=%p\n", pc); printf("pc+1=%p\n", pc + 1);//pc存储的char类型,pc+1,地址往后移动一个类型大小,大小为char的大小 1字节 printf("pi=%p\n", pi); printf("pi+1=%p\n", pi + 1);//pi存储的int类型,pi+1,地址往后移动一个类型大小,大小为int的大小 4字节 return 0; }
指针的解引用:指针的类型决定了,对指针解引用的时候有多大的权限(能操作几个字节)
#define _CRT_SECURE_NO_WARNINGS #include <stdio.h> int main() { int n = 0x11223344; char* pc = (char*)&n; int* pi = &n; //pc和pi指向同一块空间&n *pc = 0;//pc是char类型,只能操作一个字节,即它赋值为0只是让第一个字节赋值为0,其他三个字节没有变,结果没让n为0 *pi = 0;//pi是int类型,可以操作四个字节,即四字节全赋值为0,结果让n为0 return 0; }
3.野指针
野指针概念: 野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)
野指针的形成原因:
①指针未初始化 ②指针越界访问 ③指针指向的空间释放
//①指针未初始化 #include <stdio.h> int main() { int *p;//局部变量指针未初始化,默认为随机值 *p = 20; return 0; } //②指针越界访问 #include <stdio.h> int main() { int arr[10] = {0}; int *p = arr; for(int i=0; i<=11; i++) { //当指针指向的范围超出数组arr的范围时,p就是野指针 *(p++) = i; } return 0; } //③指针指向的空间释放 #include<stdio.h> int main() { int* a=(int)malloc(sizeof(int)*4); int *p=a; free(a); //如果后续再使用*p就会引发野指针问题,因为p所指向的内存空间已经被free清理了 return 0; }
避免野指针的方法:
- 指针初始化
- 小心指针越界
- 指针指向空间释放时就赋值为NULL
- 避免返回局部变量的地址
- 指针使用之前检查有效性
4.指针运算
指针运算的常见情况:①指针±整数 ②指针-指针
//①指针+-整数---分为两种情况:*(p+i)是由首元素地址往后移动i个元素地址;*p+i是*p的元素去加i #define _CRT_SECURE_NO_WARNINGS #include<stdio.h> int main() { int arr[10] = { 0 }; int* p = arr; int length = sizeof(arr) / sizeof(arr[0]); for (int i = 0; i < length; ++i) *(p + i) = i; for (int i = 0; i < length; ++i) printf("%d ", *(p + i)); printf("\n"); int a=*p + 1; printf("a=%d\n", a); a = *(p + 2) + 3; printf("a=%d\n", a); return 0; }
//②指针-指针---前提条件:两个指针指向同一块空间才可以 #define _CRT_SECURE_NO_WARNINGS #include<stdio.h> int main() { int arr[10] = { 0 }; printf("%d\n", &arr[0] - &arr[9]); printf("%d\n", &arr[9] - &arr[0]); return 0; }
补充:允许指向数组元素的指针与指向数组最后一个元素后面的那个内存位置的指针比较,但是不允许与指向第一个元素之前的那个内存位置的指针进行比较
5.指针与数组
#define _CRT_SECURE_NO_WARNINGS #include <stdio.h> int main() { int arr[10] = {1,2,3,4,5,6,7,8,9,0}; printf("%p\n", arr); printf("%p\n", &arr[0]); return 0; }
可见数组名和数组首元素的地址是一样的
既然可以把数组名当成地址存放到一个指针中,我们使用指针来访问就成为可能
#define _CRT_SECURE_NO_WARNINGS #include <stdio.h> int main() { int arr[] = {1,2,3,4,5,6,7,8,9,0}; int *p = arr; //指针存放数组首元素的地址 int sz = sizeof(arr)/sizeof(arr[0]); for(i=0; i<sz; i++) { printf("&arr[%d] = %p <====> p+%d = %p\n", i, &arr[i], i, p+i); } return 0; }
结论: p+i 其实计算的是数组 arr 下标为i的地址
6.二级指针
指针变量也是变量,是变量就有地址,那指针变量的地址存放在哪里? —这就是二级指针
//*ppa通过对ppa中地址进行解引用,这样就找到了pa,*ppa其实访问的就是pa int a = 10; int *pa=&a int **ppa=&pa; *ppa = &a;//等价于 pa = &a; //**ppa 先通过 *ppa 找到 pa ,然后对 pa 进行解引用操作: *pa ,找到的是a **ppa=30;//*ppa等价于pa,**ppa等价于*pa,这里就等价于a=30;
7.指针数组
指针数组是指针还是数组?—是存放指针的数组
int arr1[5];//arr1数组中存放5个int类型元素---整形数组 char arr2[6];//arr2数组存放6个char类型元素---字符数组 int* arr3[5];//arr3数组存放5个int*类型元素---指针数组