【指针】内含超多图解!通俗易懂!(入门必看!)
【指针】内含超多图解!通俗易懂!(入门必看!)
指针是C语言中的一个重要概念,它允许程序员直接操作内存地址,从而实现更高效的数据处理。本文将通过通俗易懂的语言和大量图解,帮助读者系统地学习和掌握指针的基本概念、操作符、运算、使用场景、与数组的关系、二级指针、函数指针等多个方面。
1、初始指针
首先,我们通过一个例子认识一下指针:
假设你要到朋友的学校找他,你不知道他的宿舍号是多少,你只能一个一个宿舍的找,知道你找到他位置。
但是如果你提前知道了他的宿舍号,你就可以直接到对应的楼层,对应的房间。这样就可以大大节省了时间。
同理,我们知道计算机上CPU(中央处理器)在处理数据的时候,需要的数据是在内存中读取的,现在内存被划分为一个个的内存单元,每个内存单元的大小取1个字节。当我们需要寻找内存中的某个数值时,一个一个内存单元挨着去找,这样的效率会很低。
但是如果知道了该数值在内存中的地址,此时是不是就可以直接访问该地址,从而大大提高了效率!
这个大大提高效率的地址就是这篇文章要讲的指针了!
2、指针变量和两个关键操作符
取地址操作符 &
解引用操作符 *
2.1 “&”取地址操作符
我们知道a为int类型,在内存中占4个字节,而这四个字节的地址分别是:
0x0000003CC494F794
0x0000003CC494F795
0x0000003CC494F796
0x0000003CC494F797
那我们如何能得到a的地址呢?
这里就得学习一个操作符(&)-取地址操作符
此时,&a取到的是四个地址中最小的地址,虽然整型变量占用4个字节,我们只要知道了第一个字节地址,顺藤摸瓜访问到4个字节的数据也是可行的。
2.2 指针变量
那我们通过取地址操作符(&)拿到的地址是一个数值,比如:0x006FFD70,这个数值有时候也是需要存储起来,方便后期再使用的,那我们把这样的地址值存放在哪里呢?答案是:指针变量中。
指针变量也是一种变量,这种变量就是用来存放地址的,存放在指针变量中的值都会理解为地址。
int a=1;
int* p=&a;
2.3 “ * ”解引用操作符
现在我们数值的地址存放在指针变量中,那么我们如何使用它呢?
在现实生活中,我们使用地址要找到一个房间,在房间里可以拿去或者存放物品。
C语言中其实也是一样的,我们只要拿到了地址(指针),就可以通过地址(指针)找到地址(指针)指向的对象,这里必须学习一个操作符叫解引用操作符(*)。
int main()
{
int a = 1;
int* p = &a;
printf("%d", *p);//p存放a的地址,*p就找到了a存放的空间,*p其实就是a变量了
return 0;
}
2.4 小结
被指向的类型 指针类型
int int*
char char*
… …
3.指针运算
int arr[5]={1,2,3,4,5};
int * p=&arr[0];
下面以这一个数组为例讲解指针运算。
3.1 指针±整数
*p=arr[0]=1
*(p+1) =arr[1]=2
*(p+2)=arr[2]=3
…
此时,由于p为int类型的指针,所以p+1跳过4个字节。
同理,如果p为char类型的指针,p+1跳过1个字节。
3.2 指针-指针
3.3 指针的关系运算
假设有指针变量 p1、p2。
1.p1 > p2 表示 p1指向的存储地址是否大于 p2 指向的地址
2.p1 == p2 表示 p1 和 p2 是否指向同一个存储单元
3.p1 == 0 (p1==NULL)和 p1 != 0 (p1!=NULL)表示 p1是否为空指针
int main()
{
int arr[5] = { 1,2,3,4,5 };
int* p1 = arr[0];
int* p2 = NULL;
if (p1 > p2&&p1!=0&&p2!=0)
printf("p1指向的地址大于p2指向的地址。");
else if (p1 == p2&&p1!=NULL)
printf("p1和p2指向同一存储单元。");
else if (p1 == NULL || p2 == NULL)
printf("存在空指针。");
else
printf("p2指向的地址大于p1指向的地址。");
return 0;
}
4 . 指针的使用和传址调用
首先通过一个例子给大家讲解。
4.1 strlen函数的模拟实现
int My_strlen(char* p, int sz)//实参为char*类型的指针,形参为char*类型
{
int count = 0;
while (*p++)//运算符的优先级,先解引用,再++。p+1跳过一个字节。
//当p指向'\0'时循环停止
{
count++;
}
return count;
}
int main()
{
char arr[] = "asdff";
int sz = sizeof(arr) / sizeof(arr[0]);
printf("%d", My_strlen(arr, sz));//arr代表首元素地址,类型为char*
return 0;
}
4.2 传值调用和传址调用
传值调用
学习指针的目的使用指针解决问题,那什么问题,非指针不可呢?
例如:写一个函数,交换两个整型变量的值一番思考后,我们可能写出这样的代码:
void Swap1(int x, int y)
{
int tmp = x;
x = y;
y = tmp;
}
int main()
{
int a = 0;
int b = 0;
scanf("%d %d", &a, &b);
printf("交换前:a=%d b=%d\n", a, b);
Swap1(a, b);
printf("交换后:a=%d b=%d\n", a, b);
return 0;
}
当我们运行代码时会发现,代码并没有达到交换的效果。
该函数进行的是形参的交换,而形参只是实参的一份拷贝,形参的数值改变不会使实参数值发生改变。
传址调用
如何使用函数实现交换呢?
在main函数中将a和b的地址传递给Swap函数,Swap函数面边通过地址间接的操作main函数中的a和b,并达到交换的效果就好了。
void Swap2(int* px, int* py)
{
int tmp = 0;
tmp = *px;
*px = *py;
*py = tmp;
}
int main()
{
int a = 0;
int b = 0;
scanf("%d %d", &a, &b);
printf("交换前:a=%d b=%d\n", a, b);
Swap2(&a, &b);
printf("交换后:a=%d b=%d\n", a, b);
return 0;
}
5. 指针与数组
5.1数组名的理解
数组名为首元素的地址。
5.2 冒泡排序
冒泡排序的核心思想就是:两两相邻的元素进行比较。
void bubble_sort(int arr[], int sz)
{
int i = 0;
for (i = 0; i < sz - 1; i++)
{
int j = 0;
for (j = 0; j < sz - i - 1; j++)
{
if (arr[j] > arr[j + 1])
{
int tmp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = tmp;
}
}
}
}
int main()
{
int arr[] = { 3,1,7,5,8,9,0,2,4,6 };
int sz = sizeof(arr) / sizeof(arr[0]);
bubble_sort(arr, sz);
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
return 0;
}
注意:一维数组传参,形参部分可以写成数组的形式(本质上传递的仍是数组首元素的地址),也可以写成指针的形式。
6. 二级指针和指针数组
6.1 二级指针
指针变量也是变量,是变量就有地址,那指针变量的地址存放在哪里?
这就是 二级指针 。
6.2 指针数组
指针数组,顾名思义就是存放指针的数组。
指针数组的每个元素都是地址,又可以指向一块区域。
6.3 数组指针
int(*p)[10];
解释:p先和 * 结合,说明p是一个指针变量变量,然后指针指向的是一个大小为10个整型的数组。所以
p是一个指针,指向一个数组,叫数组指针。
这里要注意:[]的优先级要高于* 号的,所以必须加上()来保证p先和*结合。
6.4 二级指针实现指针数组
#include <stdio.h>
int main()
{
int arr1[] = { 1,2,3,4,5 };
int arr2[] = { 2,3,4,5,6 };
int arr3[] = { 3,4,5,6,7 };
//数组名是数组首元素的地址,类型是int*的,就可以存放在parr数组中
int* parr[3] = { arr1, arr2, arr3 };
int i = 0;
int j = 0;
for (i = 0; i < 3; i++)
{
for (j = 0; j < 5; j++)
{
printf("%d ", parr[i][j]);
}
printf("\n");
}
return 0;
}
上述代码模拟出了二维数组的效果,实际上并非完全是二维数组,因为每一行并非是连续的。
7. 函数指针
函数指针变量应该是调用来存放函数地址的,未来通过地址能够调用函数的。
下面是函数指针类型的格式:
了解了函数指针类型,下面尝试一下使用函数指针吧!
#include <stdio.h>
int Add(int x, int y)
{
return x + y;
}
int main()
{
int(*pf)(int, int) = Add;
printf("%d\n", (*pf)(1, 3));//输出结果 4
printf("%d\n", pf(3, 4));//输出结果 7
return 0;
}
8. const的使用
const int *p *p不可改变(指针指向的值不可改变),p可以改变(指针可以指向不同地址)
int * const p *p可以改变(指针指向的值可以改变),p不可改变(指针不可指向不同地址)
int const * p *p不可改变(指针指向的值不可改变),p可以改变(指针可以指向不同地址)
总结:const在*左边时 *p不可改变(指针指向的值不可改变),p可以改变(指针可以指向不同地址)。
const在右边时p可以改变(指针指向的值可以改变),p不可改变(指针不可指向不同地址)。
好了,这篇文章结束了!但是指针的学习还没有结束,还有结构体和动态内存方面的知识!
希望大家点赞+收藏,有什么疑问可以评论区讨论呦~~~~~
本文原文来自CSDN博客