在c语言的学习中,我们都跳不过去一个东西——指针,那什么是指针呢?我们先来了解一下。
指针是内存中一个最小的单元编号,也就是地址,我们平时说的指针通常是指指针变量,用来存放内存地址的变量。我们就可以把大内存比作一个公寓,每个小内存相当于一个小房子,每个小房子都有一个自己的门牌号,这就相当于地址。一个小的内存单元只有一个字节,而在32位机器中就会有2的32次方种不同的编号,相当于2的32次方字节的内存,在64位机器中就有2的64次方的内存,每个内存单元都有一个编号,这个编号也被称为地址。
当我们在C语言中创建一个变量、数组时,都要在内存上开辟空间。那我们怎么找到变量在计算机中的位置呢,我们可以用取地址操作符&来找到对应的地址。
int main(void) { int a = 0; int arr[12]; printf("%p\n", &a); printf("%p\n", arr); return 0; }
指针是用来存放地址的,所以我们也可以使用指针变量将对应的地址打印出来。
int main(void) { int a = 0; int* p = &a; printf("%p\n", p); printf("%p\n", &a); return 0; }
机器的地址都是由二级制数进行编号的,计算机中就有32个电压线分别代表0或1,那一个地址由32位不同的电压线进行自由组合,那指针{地址}的大小就是32bit也就是4个字节。同样64位机器的指针大小就是8个字节。
指针和指针类型
在变量类型中,我们就有不同的变量类型,有整型、浮点型、字符型……那我们的指针类型也会有这么多的类型。
char* (字符型指针)
int* (整型指针)
float* (单精度浮点型指针)
double(双精度浮点型指针)
short* (短整型指针)
long* (长整型指针)
……
无论在程序中使用哪种类型的指针,在32位机器下都是4个字节大小。
int main(void) { printf("%d\n", sizeof(int*)); printf("%d\n", sizeof(char*)); printf("%d\n", sizeof(float*)); printf("%d\n", sizeof(long*)); printf("%d\n", sizeof(short*)); printf("%d\n", sizeof(double*)); }
当在64位机器下就是8个字节大小。
在创建变量的时候,我们就是考虑要存放数据的大小才考虑使用不同的变量类型的,那创建指针变量时因为指针的大小都相等,我们是不是不用考虑指针类型,随便一个就可以使用呢?
int main(void) { int a = 0X11223344;//0X开头是16进制的数 int* p = &a; *p = 0; }
我们通过监视内存可以看到a的内存已经存储到机器中去了,当我们使用*p = 0时将改变a的内容。
成功将a中的值改成0。
但是当我们没有正确对应的使用变量时,将会怎样呢?
int main(void) { int a = 0X11223344;//0X开头是16进制的数 char* p = &a; *p = 0; }
现在我们使用char类型的指针改变a的值,会不会成功呢?
当我们使用char*指针时只改变了a中的一个字节的值 。我们就可以推出来:int*的指针解引用可以访问4个字节,char*访问可以访问1个字节。
我们就可以总结出:指针类型可以决定指针解引用的时候访问多少个字节(指针的权限)。
int main(void) { int a = 0X11223344; int* p = &a; char* pa = &a; printf("%p\n", p); printf("%p\n", p + 1); printf("%p\n", p); printf("%p\n", p + 1); return 0; }
a 的地址是007AF8FC,当int*加1后a的地址跳过了四个字节,当char*加1后a的地址跳过了1个字节。
我们可以总结出:指针类型决定指针+/-操作时的步长。
野指针
当我们在创建一个指针变量时或者在使用一个指针变量时,未初始化指针或丢失了指针指向的内容时,我们把这种指针叫做野指针。
int main(void) { int* p; *p = 20; printf("%d\n", *p); return 0; }
当我们没有初始化指针时,指针指向的内容是随机的,我们在用指针赋值或改变时,会对计算机中未知的东西改变,引起程序的bug。像形成野指针的原因有很多:
1.指针未初始化
int main(void) { int* p; *p = 100; return 0; }
2. 指针越界访问
int main(void) { int arr[10] = { 0 }; int* p = arr; int i = 0; for (i = 0; i <= 11; i++) { *(p++) = i; } return 0; }
指针指向数组的第一个元素,随着for循环指针依次向后指向数组中的元素,当最后一次移动时已经超过数组的长度,指针就变成野指针。
3.指针指向的空间释放
int* test() { int a = 10; return &a; } int main() { int *p = test(); printf("%d\n", *p); return 0; }
指针指向函数中a的地址,但是在出函数时a的空间将被销毁,所以指针也变成野指针。
所以我们一定不要让指针变成野指针,当我们在不知道怎么用指针的时候,可以使用类似int*p = NULL来定义或重赋值。
那如何避免野指针呢?
1.指针的初始化
2.小心指针越界
3.指针指向空间释放,及时用NULL
4.避免返回局部变量的地址
5指针使用之前检查有效性
指针运算
指针的运算分为三种:
1.指针+/-整数
2.指针-指针
3.指针的关系运算
指针+/-整数一般运用在数组中,我们使用数组时会运用下标,我们也可以用指针代替
int main() { int arr[10] = { 0 }; int* p = &arr[0]; int i = 0; int sz = sizeof(arr) / sizeof(arr[0]); for (i = 0; i < sz; i++) { printf("%d ", *p); p++; } return 0; }
当p+1时指针就会指向数组中的下一个元素。
指针-指针
int main(void) { int arr[10] = { 0 }; printf("%d\n", &arr[9] - &arr[0]); return 0; }
这个数组的大小为9,当第一个数组元素地址减去最后一个数组元素的地址就是9。
说明指针-指针得到的绝对值就是指针和指针之间的元素个数。前提是两个指针指向同一个空间。
指针的关系运算实际就是比较指针的大小,但是指针的比较是有规律的。第一个代码是正确的,第二个代码是错误的。
#define N_VALUES = 5 for (vp = &values[N_VALUES]; vp > &values[0];) { *--vp = 0; }
#define N_VALUES = 5 for (vp = &values[N_VALUES - 1]; vp >= &value[0]; vp--) { *vp = 0; }
标准:允许指向数组元素的指针与指针数组最后一个元素后面的那个内存位置的指针比较,但是不允许与指向的第一个元素之间的那个内存位置的指针进行比较。
指针和数组
指针和数组之间是什么关系呢?指针变量就是指针变量,不是数组,指针变量的大小就是4/8个字节,专门是用来存放地址的。数组不是指针!!!
联系:数组名其实就是数组首元素的地址,数组名==地址==指针。
先看一个例子:
int main(void) { int arr[10] = { 1,2,3,4,5,6,7,8,9,10 }; int sz = sizeof(arr) / sizeof(arr[0]); for (int i = 0; i < sz; i++) { printf("%p\n", &arr[i]); } return 0; }
数组元素的地址都是连续的,之间差一个数组类型的大小。
当int *p = arr时,arr[i] = *p + i,指针就可以代替其中的数组
nt main(void) { int arr[10] = { 1,2,3,4,5,6,7,8,9,10 }; int sz = sizeof(arr) / sizeof(arr[0]); int* p = arr; for (int i = 0; i < sz; i++) { printf("%p == %p\n", &arr[i], (p + i)); } return 0; }
数组名是数组首元素的地址,但是有两个例外:sizeof(数组名)和 &数组名。
二级指针
我们刚才说的都属于一级指针,一级指针可以存放地址,当创建指针时指针也会占用4/8字节的内存,所以指针也会有地址,这时候我们如果需要这个一级指针的地址就需要一个指针类型的指针进行接收(二级指针),所以二级指针可以理解为指针的指针。
int main(void) { int a = 10; int* p = &a; int* pp = &p; printf("%d\n", a); return 0; }
p是一级指针变量,指针变量也是变量,变量是在内存中开辟空间的,是变量就有地址。pp就是二级指针变量,二级指针变量就是用来存放一级指针变量的地址。
指针数组
我们之前学过一些类型的数组,比如整型数组,字符数组等等,这些数组就是存放一些数据类型的。那顾名思义指针数组就是存放指针的数组,数组元素都是指针变量。
#include<stdio.h> int main(void) { int arr1[] = { 1,2,3,4,5 }; int arr2[] = { 2,3,4,5,6 }; int arr3[] = { 3,4,5,6,7 }; int* parr[] = { arr1,arr2,arr3 }; for (int i = 0; i < 3; i++) { for (int j = 0; j < 5; j++) { printf("%d ", *(*(parr + i)+j)); } printf("\n"); } return 0; }
因为数组名就是数组首元素的地址,如果有多个数组,我们就可以将多个数组放入指针数组中进行处理,将这些数组一一打印出来,也可以使用parr[i][j]来打印数据。