目录
一、什么是指针
在了解指针之前,我们首先要知道什么是内存。内存是计算机硬件,是用来存放数据的硬件。计算机将内存划分为一个个小的内存单元,同时对其编号,这样就能有效管理内存。在空间划分实践中,一个内存单元的大小为为一个字节,每个内存单元都有编号叫做内存编号,内存编号就是计算机中每个内存单元的地址,而存放这些地址的变量在C语言中就被称为指针。指针就是指向内存地址,指针变量就是用来存放内存地址的变量,地址唯一标识一块内存空间。指针和其他变量或常量一样,在使用指针存储其他变量地址之前,必须要对其进行声明。
通俗的来讲就是,假设有一栋楼(计算机),这栋楼里有无数个房间 ( 内存) , 我们在楼里申请的一个房间 a(变量), 房间 a 里面放了一百万(对a赋值为100), a 的房间号是00FEEC1C (地址) ,而 a 的房间号放在了这栋楼里的房间 p (指针) 里面,那么这个房间 p 放的的房间 a 的地址,就称这个 p 是 a 的指针。
由上面我们可以看到,指针p的值和a的地址是一样的,所以p本质上就是一个变量,只不过这个变量是用来放地址的,叫做指针变量。这里的int* 是p的类型,就像a的类型是int一样。在这里我们需要注意的是,指针变量所占的字节大小和类型无关,虽然 int 型占 4 个字节,char型占 1 个字节,但指针变量所占字节大小都是固定的,在32位平台下一个指针变量占4个字节,在64位平台下一个指针变量占8个字节。
二、指针变量
2.1指针变量
指针变量的声明一般格式为 ---type * name;具体的类型有以下几种
int a; char b; float c; double d; int * pa=&a; // 一个整型的指针 char * pb=&b; // 一个字符型的指针 float * pc=&c; // 一个浮点型的指针 double * pd=&d; // 一个 double 型的指针
这里的int *,char *是指针的类型,p是指针变量的名字。*前后的空格有没有都可以。当我们声明一个指针变量之后,我们默认的就是存放在p里面的值是地址。我们要注意的是指针变量的类型是依据他所指向的变量类型,如果这个变量是int型,那么指向这个变量的指针就是int* 类型,同理,char,float,double也一样。
在指针类型中也有一种特殊的指针类型——viod*类型,是一种无具体类型的指针,它可以用来接收任意类型的地址,但是也有一定的局限型,无法进行加减整数以及解引用运算,其一般多用于函数参数部分,用来接收不同类型数据的地址。
2.2指针变量解引用
我们都知道取地址运算符&,其作用就是取出变量的地址进行操作("比如说取出a的地址赋给p指针 p=&a ),但是对于常量表达式、寄存器变量不能取地址,因为它们存储在存储器中没有地址,所以取地址只能对变量。而我们要对指针进行操作那么就需要用到指针运算符 * ,它与&为逆运算,使用*p就是获取储存在变量中的内容,这步操作我们称为解引用。
由上面我们可以看到,打印出的指针p的值和a的地址是一样的,所以p本质上就是一个变量,只不过这个变量是用来放地址的,叫做指针变量。在打印的时候使用*p就是解引用p,此时*p==a。
2.3 const修饰指针变量
我们都知道变量是可以直接被修改的,那么当我们通过解引用指针去修改指针所指向的变量的内容时也是可行的,那么当我们在定义指针时使用了const修饰时是否还可以修改呢?下面这串代码给了详细的解释
//无法修改所指向变量的内容,但是可以让指针指向别的变量 int a = 100; int b = 111; int const* pa = &a; //*pa = 10; pa = &b; //无法修改所指向变量的内容,也无法让指针指向别的变量 int a = 100; int b = 111; const int* pa = &a; //*pa = 10; pa = &b; //可以修改所指向变量的内容,但是不可以让指针指向别的变量 int a = 100; int b = 111; int *const pa = &a; *pa = 10; //pa = &b; //无法修改所指向变量的内容,也无法让指针指向别的变量 int a = 100; int b = 111; int const*const pa = &a; //*pa = 10; //pa = &b;
综合上面的代码,当const修饰指针变量时,我们可以得出以下结论
①如果 const 放在 * 的左边,那么修饰的是指针所指向的变量的内容,所以无法修改所指向变量的内容,但是可以让指针指向别的变量
②如果 const 放在 * 的右边,那么修饰的就是指针变量本⾝,所以无法让指针指向别的变量,但是可以修改所指向变量的内容
三、指针运算
3.1 指针加减整数
数组在内中是连续存放的,所以只要知道了第一个元素的地址,那么后面所有的元素就都可以找到。所以程序中多使用指针代替数组,因为变量指针可以递增,而数组不能递增,那么就数组可以看成一个指针常量。指针的每一次递增,它其实会指向下一个元素的存储单元;指针的每一次递减,它都会指向前一个元素的存储单元。指针在递增和递减时跳跃的字节数取决于指针所指向变量数据类型长度。所以指针加减整数可以看作递增或递减一个整数,以下面的程序为例,指针加减整数打印数组内容:
int main() { int arr[5] = { 1,3,5,7,9 }; int* p = &arr; int i = 0; int sz = sizeof(arr) / sizeof(arr[0]); for (i = 0; i < sz; i++) { printf("%d ", *(p + i)); } return 0; } int main() { char arr[] = "csdn666"; int sz = strlen(arr); char* p = &arr[sz - 1]; for (int i = 0; i <sz; i++) { printf("%c ", *(p - i)); } return 0; }
3.2 指针加减指针
我们要知道,如果指针相加减那么其必要条件是这两个指针所指向的是同一空间,而指针-指针的绝对值指的是两个指针之间元素的个数。下面我们就举个例子实现my_strlen函数
//指针+ -指针实现my_srelen函数 int my_strlen(char *p) { int count = 0; while (*p != '\0') { p++; count++; } return count; } int main() { char arr[] = "csdn666"; printf("%d\n", my_strlen(arr)); return 0; }
3.3指针的比较
指针可以用关系运算符进行比较,如 ==、< 和 >。下面代码 p1 和 p2 指向同一个数组的两个不同的元素,当指针p1的值小于p2时,就进入while循环打印p1所指向的值。
int main() { int arr[5] = { 1,2,3,4,5 }; int* p1 = &arr; int* p2 = &arr[4]; while (p1 <= p2)//指针大小的比较 { printf("%d ", *p1); p1++; } return 0; }
四、野指针
4.1 野指针成因
野指针是指向非法内存地址的指针,指向的位置是随机的,不正确且无限制的,意为无法使用的指针。
//指针未初始化 int main() { int* p;//这里的指针未进行初始化,所指向的位置是随机的 *p = 6;//所以*p就会非法访问 return 0; } //指针越界访问 int main() { int arr[5] = { 0 }; int* p = &arr[0]; for (int i = 0; i < 8; i++) { *p = i; p++;//当指针所指向的范围超过数组arr的范围时,p就是野指针 } return 0; } //指针指向的空间释放 int* test() { int a = 188;//变量a的地址只在test()函数内有效 return &a;//当把a的地址传给指针p时 } int main() { int* p = test();//出了test函数,变量a的空间地址就会释放,此处的p就变成了野指针 printf("%d\n", *p); return 0; }
4.2 合理规避野指针
1. 指针初始化,可以初始赋值为空指针NULL
2. 小心指针越界,不要访问超出申请的空间的范围
3. 指针指向空间释放,也可以使用空指针,及时置NULL
4. 避免返回局部变量的地址——也即是避免返回栈空间上的地址
5. 指针使用之前检查有效性
五、总结
以上就是今天的全部内容了,主要是简单的介绍了指针变量的定义与简单运算,下篇文章我会介绍指针与数组、指针与函数,多级指针。如有不足欢迎家人们评论区批评指正,如果这篇文章对你有用的话,可以给我来个一键三连嘛