前言
提示:
/* 本文章 主要记录个人学习,若有误解,还请指出。*/
提示:以下是本篇文章正文内容,下面案例可供参考
一、什么是指针
1.指针
在C语言里,变量存放在内存中,而内存其实就是一组有序字节组成的数组,每个字节有唯一的内存地址。CPU 通过内存寻址对存储在内存中的某个指定数据对象的地址进行定位。这里,数据对象是指存储在内存中的一个指定数据类型的数值或字符串,它们都有一个自己的地址,而指针便是保存这个地址的变量。简单说:指针是用来存放内存地址的指针变量。
简单理解指针的两个要点:
指针是内存中一个最小单元的编号,也就是地址。
指针变量是用来存放地址的指针变量。
2.指针变量概念
定义:可以通过&(取地址操作符)取出某份数据的内存起始地址,这样的一份数据可以是数组、字符串、函数,这个变量就是指针变量。
3.定义指针变量
#include<stdio.h> int main() { int a = 10; //在内存中开辟一块内存空间 int* p = &a; //首先对变量a取地址,&取地址操作符,a变量属于int类型,占用4个内存空间, //将a的四个字节中的第一个字节地址存放在p变量中,而p就是一个指针变量。 return 0; }
4.指针变量的大小
#include<stdio.h> int main() { int* pi; char* pc; float* pf; double* pd; printf("pi=%d\n",sizeof(pa)); printf("pc=%d\n",sizeof(pc)); printf("pf=%d\n",sizeof(pf)); printf("pd=%d\n",sizeof(pd)); /* pi=4 pc=4 pf=4 pd=4 不管指针类型如何,大小均为4个字节 */ }
注:1.一个内存单元的大小为1个字节;
2. 存放在指针中的值都会被当做地址处理;
3. 在32位机器上,地址是由32个0或1组成的二进制bite序列,在64位机器上,地址是由64个0或1组成的二进制bite序列。故指针的大小在32位平台上为4个字节,在64位平台上为8个字节,指针大小与指针类型无关。
5.指针类型
上面我们提到过,p就是一个指针变量,那么指针类型又分为哪些?
#include<stdio.h> int main() { char *pc = NULL; int *pi = NULL; short *ps = NULL; long *pl = NULL; float *pf = NULL; double *pd = NULL; //这里可以看到指针的变量是data_type + *; /* char*类型的指针是为了存放char 类型变量的地址; int*类型的指针是为了存放int类型变量地址; short*类型的指针是为了存放short类型变量地址; …… */ return 0; }
那么指针类型的意义是什么?
#include<stdio.h> int main() { /*指针类型决定访问权限*/ int a = 0x11223344;//0x表示十六进制,值为四个字节 int* p = &a; *p = 0; //将0赋值给*p(*为解引用符,用来获取指针指向的数据变量a.) //按F10调试,查看调试->窗口->内存(输入&a进行调试),内存的值变为 00 00 00 00,我们会值的4个字节全部变成了0 char* pc = &a; *pc = 0;//按F10调试,查看调试->窗口->内存(输入&a进行调试),内存的值变为 00 33 22 11,我们会发现值的第1个字节变成了0 return 0; }
再举一个例子
#include<stdio.h> int main() { int arr[10] = {0}; int* p = arr; //数组名为数组首元素的地址 char* pc = arr; printf("p = %p",p); printf("p+1 = %p",p+1); printf("pc = %p",pc); printf("pc+1 = %p",pc+1); /* p = 00CFFD30 p+1 = 00CFFD34 //p+1跳过了4个字节,为一个int类型 pc = 00CFFD30 pc+1 = 00CFFD31 //pc+1跳过了1个字节,为一个char类型 */ return 0; }
指针类型的意义:指针类型决定了指针解引用的访问权限有多大。
int类型的指针访问权限为4个字节
char类型的指针访问权限为1个字节
float类型的指针访问权限为4个字节
double类型的指针访问权限为8个字节
二、什么是野指针
1.野指针
概念:野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)指针变量在定义时如果未初始化,其值是随机的,指针变量的值是别的变量的地址,意味着指针指向了一个地址是不确定的变量,此时去解引用就是去访问了一个不确定的地址,所以结果是不可知的。
2.野指针的分类
1.指针未初始化
#include<stdio.h> int main() { int* p;//p是一个局部的指针变量,局部变量不进行初始化的话,默认是随机值,而随机值是不用进行申请的, //故*p访问的地址可能不属于自己,指向的内存单元可能就无法访问。 *p = 20; return 0; }
2. 指针出现越界访问
数组 (指针)越界访问,是指使用了超过有效范围的偏移量。 如只分配了10个元素的空间,但是访问了第11个元素,就属于越界。
#include<stdio.h> int main() { int arr[10] = {0}; int* p = arr; int i = 0; for(i = 0;i <= 10;i++) { //当指针指向的范围超出了数组arr的范围时,p就是野指针。 *(p++) = i; } return 0; }
3. 指针指向的空间被释放
#include<stdio.h> int* teset() { int i = 10; //i变量在进入test()函数时被创建,地址会被分配,离开test()函数被销毁,内存地址就会被释放。 return &i; } int main() { int* p = test(); *p = 20; //这里就属于非法访问了 return 0; }
4. 避免野指针
但是如何去规避野指针的出现呢?
1.进行指针初始化
2.小心指针越界
3. 指针指向空间释放及时置为NULL值
4. 避免返回局部变量的地址
5. 指针使用之前检查有效性
三、指针的运算
1.指针的加减运算
#include<stdio.h> int main() { int arr[10] = { 1,2,3,4,5,6,7,8,9,10 }; int* ptr1 = arr;//数组首元素的地址 int* ptr2 = &arr[2];//数组下标为2的元素的地址 int* ptr3 = ptr2 + 7;//指针加上整数 (10=3+7) printf("ptr2 = %d\n", *ptr2); //ptr = 3 printf("ptr3 = %d\n", *ptr3); //ptr = 10 return 0; }
2.指针减去一个整数
#include<stdio.h> int main() { int arr[10] = { 1,2,3,4,5,6,7,8,9,10 }; int* ptr1 = arr;//数组首元素的地址 int* ptr2 = &arr[7];//数组下标为7的元素的地址 int* ptr3 = ptr2 - 2;//指针减去整数 (6=8-2) printf("ptr2 = %d\n", *ptr2); //ptr = 8 printf("ptr3 = %d\n", *ptr3); //ptr = 6 return 0; }
3.指针减去指针
指针减去指针得到的是两个指针之间的元素个数
#include<stdio.h> { int arr[5] = {1,2,3,4,5}; int* ptr1 = &arr[4]; //5 int* ptr2 = &arr[1]; //2 printf("%d",ptr1 - ptr2); //3 return 0; }
两个指针指向不同的空间,再相减会报错
#include<stdio.h> int main() { int arr[10] = {1,2,3,4,5,6,7,8,9,10}; char c[] = {0}; /*指针和指针相减的前提是两个指针指向同一块空间 */ printf("%d\n",&arr[9] - &c[0]); //error 运行会报错,因为指向了不同的数组空间 return 0; }
2.指针的关系运算
#include<stdio.h> int main() { int values[5] = {1,2,3,4,5}; int* ptr = 0; for(ptr = &values[5];ptr > &values[0];) { *--ptr = 0; } return 0; }
按F10在调试->窗口->监视,进行调试。
代码简化,将代码修改如下:
#include<stdio.h> int main() { int values[5] = {1,2,3,4,5}; int* ptr = 0; for(ptr = &values[4];ptr >= &values[0];ptr--) { *ptr = 0; } return 0; }
按F10调试
实际上在绝大部分的编译器上是可以顺利完成任务的,然而我们还是应该避免这么写,实际标准不保证它可行。
标准规定:允许与指向数组元素的指针与指向数组最后一个元素后面的内存位置的指针比较,但是不允许与指向第一个元素之前的内存位置的指针进行比较。
四、指针和数组
#include<stdio.h> int main() { int arr[10] = { 0 }; int* p = arr; int i = 0; for(i=0;i<10;i++) { printf("%p <==> %p\n", &arr[i], p + i); } return 0; }
输出结果:
&arr和p+i都表示为数组下标为i的元素的地址
利用指针方法打印数组的元素
#include<stdio.h> int main() { int arr[10] = { 0 }; int* p = arr; int sz = sizeof(arr) / sizeof(arr[0]); //数组元素的个数 int i = 0; for (i = 0; i < sz; i++) { printf("%d ", *(p + i) = i); //0,1,2,3,4,5,6,7,8,9 } return 0; }
几种数组元素的指针表示方法,如同数学中的交换律,等价交换。
#include<stdio.h> int main() { int arr[10] = {1,2,3,4,5,6,7,8,9}; //数组名为首元素的地址 传递给指针变量p int* p = arr; printf("%p <==> %p \n", arr, p); printf("%d <==> %d \n", arr[1], p[1]);//数组下标为1的元素 printf("%d <==> %d \n", arr[2], *(p+2));//数组下标为2的元素 printf("%d <==> %d \n", *(arr+3), *(p + 3));//数组下标为3的元素 printf("%d <==> %d \n", *(4 + p), *(p + 4));//数组下标为4的元素 printf("%d <==> %d \n", arr[5], 5[arr]);//数组下标为5的元素 printf("%d <==> %d \n", p[6], 6[p]);//数组下标为6的元素 return 0; }
五、二级指针
指针变量的地址如何存放?就有了二级指针,用来存放指针变量的地址。
#include<stdio.h> int main() { int num = 2; int* pn = #//num变量的地址存放在指针变量pn中 int** ppn = &pn; //把pn的地址存放在ppn中 /* pn是一个普通指针,而ppn是个二级指针 */ printf("%d\n", *pn);//*pn == num;//解引用,得到num的值即2 printf("%p\n", *ppn);//*ppn == pn;//解引用,得到pn的值即&num printf("%d\n", **ppn);//**ppn == num;//解引用,得到num的值即2 return 0; }
六、指针数组
指针数组本质上是一个数组,用来存放指针的数组。
表现形式如下:
#include<stdio.h> int main() { int a = 10,b = 20,c = 30; int* parr[3] = {&a,&b,&c};//数组parr是一个整型指针数组,每个元素是个整型指针,指针存放a、b、c的地址。 char* pch[3] = {"c语言","c++语言","java语言"};//pch是一个char*类型指针数组,每个元素是一个char*类型的指针,这些指针存放着其对应字符串的首地址。 return 0; }