指针
- 指针是什么?
- 指针和指针类型
- 野指针
- 指针运算
- 指针和数组
- 二级指针
- 指针数组
文章目录
1.指针是什么?
指针是什么?
- 指针是内存中一个最小单元的编号,也就是地址
- 平时口语中说的指针,通常指的是指针变量,是用来存放内存地址的变量
总结:指针就是地址,口语中说的指针通常指的是指针变量。
int a = 10;
int * pa = &a;//使用&操作符取出a的地址。
//a变量占用4个字节的空间,这里是将a的4个字节的第一个字节的地址存放在pa
*pa;//通过pa找对对象a的值
指针变量 里面存放的是地址,而通过这个地址,就可以找到一个内存单元。
同时我们需要明白:
- 在32位的机器上,地址是32个0或1组成的二进制序列,那地址就得用4个字节的空间来存储,所以一个指针变量的大小就应该是4个字节。(4个字节=32比特)
- 那如果在64位机器上,如果有64个地址线,那一个指针变量的大小是8个字节,才能存放一个地址线。(8个字节=64比特)
总结:
- 指针变量是用来存放地址的,地址是唯一标示一块地址空间的。
- 指针的大小在32位平台是4个字节,在64位平台是8个字节。
2.指针和指针类型
2.1输出所有指针变量的大小
# define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
int main() {
//不管是什么类型的指针都是在创建指针变量
//指针变量是用来存放地址的
//指针变量的大小取决于一个地址存放的时候需要多大的空间
//32位机器上的地址:32bit-4byte,所以指针的大小是4个字节
//64位机器上的地址:64bit-8byte,所以指针的大小是8个字节
printf("%zu\n", sizeof (int*));
printf("%zu\n", sizeof (short*));
printf("%zu\n", sizeof (long*));
printf("%zu\n", sizeof (float*));
printf("%zu\n", sizeof (double));
printf("%zu\n", sizeof (char* ));
//左上角的X86就是32位bit,X64就是64位bit,可以调节查看指针变量大小的变化
return 0;
}
2.2不同指针解引用的区别
为变量a附上十六进制的值,我们会发现两位两位的大小为一个字节。
int main() {
int a = 0x11223344;//11 22 33 44,分别占了一个字节一共4个字节
return 0;
}
使用窗口——>内存,查看变量a所占的地址
我们发现变量a所占的首个地址是0x000000A4926FF5F4,那么44占的地址就是F4,33占的地址就是F5,22占的就是F6,11占的就是F7
我们使用指针变量pa接收变量a的地址
//取出变量a的地址
int* pa = &a;
并通过指针变量对a的值进行修改
*pa = 0;
我们可以发现a的值中4个字节的大小都进行了修改
我们这里使用其他类型的指针变量来接收整型变量a的地址,并修改变量a的值
int main() {
int a = 0x11223344;
//int* pa = &a;
//使用其他类型来接收变量a
char* pa = (char *) & a;
*pa = 0;
return 0;
}
我们发现可以正常接收
进行修改发现,只能对第一个字节进行修改
这就是使用错误的指针类型来存储数据时可行,但是使用错误的类型使用数据就会报错,比如上面的整型指针变量与字符型指针变量,都是存储变量a并进行修改,整型指针可以修改4个字节,但是字符型指针只能修改一个字节。
总结:指针变量是有意义的,指针变量的类型决定了,解引用操作的时候访问的字节个数。
就如int*可以访问4个字节,char*可以访问1个字节,double*可以访问8个字节。
2.3不同指针存储数据时的区别
使用不同类型的指针存储数据
int main() {
//指针类型的用处2
int a = 0x11223344;
int* pa = &a;
char* pc = (char*)&a;
printf("%d\n", pa);
printf("%d\n", pa+1);
printf("%d\n", pc);
printf("%d\n", pc+1);
}
输出结果:
总结:
指针的类型决定了,对指针解引用的时候有多大的权限(能操作几个字节)。
比如,char*
的指针解引用只能访问一个字节,而int*
的指针解引用就能访问四个字节。
2.4访问大小相同的指针可以混合使用吗?
同样访问字节大小相同的指针也是不能混合使用的。,比如int*和float*都是访问4个字节大小的,但是不能混合使用。
使用同样可以访问4个字节的整型和浮点型比较
使用整型指针存储数据:
int main() {
int a = 0;
int* pa = &a;
*pa = 100;
return 0;
}
查看内存结果:
存入了100的十六进制的数据。
使用浮点型指针存储数据:
int main() {
int a = 0;
//int* pa = &a;
float* pf = &a;
//*pa = 100;
*pf = 100.0;
return 0;
}
查看存储结果:
总结:
虽然int*
和float*
的解引用访问都是4个字节,但是int*
和float*
是不能混合使用的。
3.野指针
概念:野指针就是指针的位置是不可知的(随机的、不正确的、没有明确限制的)。
3.1野指针的形成
1.未初始化
int main(){
int *p;
//p没有初始化就意味着没有明确的指向
//一个局部变量不初始化。放的是随机值
*p = 100;//非法访问内存,这里的p就是野指针
}
2.指针越界访问
int main(){
int arr[10] = {0};
int * p = arr;
int i = 0;
for (i = 0;i<=10;i++){
*p = i;
p++;
}
return 0;
}
3.空间释放
局部变量(如函数test
中的变量a
)是在函数被调用时创建的,并在函数返回后被销毁。因此,当您在函数test
中返回变量a
的地址时,实际上您返回的是一个指向已经不存在(或即将被销毁)的内存区域的指针。
int* test() {
//临时变量
int a = 10;
//临时的地址
return &a;
}
int main() {
//*p在当前的地址指向临时地址
int* p = test();
//虽然可以输出,但是值其实是不稳定的
printf("%d", *p);
}
但栈区的信息一动,获取的地址就不准确了
int* test() {
//临时变量
int a = 10;
//临时的地址
return &a;
}
int main() {
//*p在当前的地址指向临时地址
int* p = test();
printf("abcd");
//栈区被动过了,输出的值就不确定了
printf("%d", *p);
}
一般不知道指针指向哪里一般会用空指针来先填充
int * p2 = NULL;
//对空指针进行修改,程序会崩溃或报错
//访问冲突有些地址是不能访问的,比如,0地址
*p2 = 100;
if(*ps != NULL){//判断是不是空指针
...
}
3.2如何规避野指针
- 指针初始化
- 小心指针越界
- 指针指向空间释放即使置NULL
- 避免返回布局变量的地址
- 指针使用之前检查有效性
4.指针运算
- 指针±整数
- 指针-指针
- 指针的关系运算
4.1指针±整数
使用指针来遍历数组
#dedfine num 5;
float arr[num];
float *vp;
for(vp = &arr[0]; vp < &arr[num];){
//这里的++针对的是vp而不是*vp,后置是先使用后赋值
//*vp++等同于*vp然后vp++
*vp++ = 0;
}
4.2指针-指针
指针-指针得到的指针和指针之间元素的个数
int main(){
int arr[10] = { 0 };
printf("%d",&arr[9] - &arr[0]);//输出:9
}
形象的表示arr[0]和arr[9]所处于的位置,它们的指针分别指向对应数据的前面
arr[9]-arr[0]求的便是之间元素的个数9
注意:
不是所有的指针都能相减
指向同一块空间的2个指针才能相减!
小应用
不使用strlen求字符串长度的方法1:
通过判断当前指针是否指向字符串的结束符号来判断的
# define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
int count_str(char* one) {
int count = 0;
while (*one != '\0')
{
count++;
one++;
}
return count;
}
int main() {
char ch[] = "abcdef";
int str_num = count_str(ch);
printf("%d", str_num);
}
不使用strlen求字符串长度的方法2:
如果我们知道’\0’的指针就可以通过指针相减来获取从头到尾之间元素的个数。
//方法2
int count_str(char* first) {
//存储第一个元素的指针
int* start = first;
//判断是否到了最后一个指针
while (*first!= 0) {
first++;
}
//返回\0指针-开始的指针地址---就是计算之间元素的个数
return first - start;
}
int main() {
char ch[] = "abcdef";
int str_num = count_str(ch);
printf("%d", str_num);
}
4.3指针的关系运算符
标准规定:
允许指向数组元素的指针与指向数组最后一个元素的后面的那个内存位置的指针比较,但不允许与指向第一个元素之前的那个内存位置的指针进行比较。
5.指针和数组
数组:一组相同类型元素的集合
指针变量:是一个变量,存放的是地址
数组名代表数组首元素的地址。
#inclue <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[1]);
}
你会发现它们的输出内容相同。
使用指针获取数组元素:
//函数的两个参数,一个是函数名称,一个是元素个数
void test(int* p,int sz){
int i = 0;
for(i=0;i<sz;i++){
//首元素地址+i。就是对应的后面元素的地址,并通过*获取元素信息
printf("%d ",*(p+i));
}
}
int main(){
int arr[10] = {0};
//传过去首元素的地址和元素个数
test(arr,10);
return 0;
}
6.二级指针
一级指针变量就是通过解引用操作一次就可以获取a的值
例如:
int main(){
int a = 10;
//pa是一个指针变量,一级指针变量
int* pa = &a;
return 0;
}
二级指针变量是用来存放一级指针变量的地址的
二级指针示例:
int main(){
int a = 10;
//pa是一个指针变量,一级指针变量
int* pa = &a;
//ppa是一个二级指针变量
int** ppa = pa;
**ppa = 20;
printf("%d",a);//这里的a已经被修改为20
return 0;
}
图示:
int * pa中的*说明pa是指针,前面的int说明pa指向的对象是int类型的
7.指针数组
指针数组:存放指针的数组就是指针数组
把指针和地址存放到一个数组里
int main(){
int a = 10;
int b = 20;
int c = 30;
int* pa = &a;
int* pb = &b;
int* pc = &c;
//parr就是存放指针的数组
//指针数组
int* parr[10] = {&a, &b, &c};
return 0;
}
使用数组打印出数据,就是使用一级指针变量的解引用操作符。
int main(){
int a = 10;
int b = 20;
int c = 30;
int* pa = &a;
int* pb = &b;
int* pc = &c;
//parr就是存放指针的数组
//指针数组
int* parr[10] = {&a, &b, &c};
int i = 0;
for(i = 0;i < 3;i++){
//遍历使用解引用操作符获取数据
printf("%d",*(parr[i]));
}
return 0;
}
小应用:
使用指针数组打印出二维数组
代码示例:
将三组数组,打印成二维数组[3][4]的样子
int arr1[4] = {1, 2, 3, 4};
int arr2[4] = {2, 3, 4, 5};
int arr3[4] = {3, 4, 5, 6};
//存储指针
int parr[3] = {arr1, arr2, arr3};
int i = 0;
for(i = 0;i < 3; i++){
int j = 0;
for(j = 0;j < 4; j++){
//打印出每个指针对应的数据
printf("%d",parr[i][j]);
}
}