概述
Hello 指针 !
- 入门
- 第一回合
- 1.指针的作用
- 2.我们怎么定义指针?
- 3.图解指针可好?
- 第二回合
- 1.指针与函数
- 2.指针与数组
- 3.指针与指针
- 4.指针与字符串
- 5.指针与结构体
- 第三回合
- 1.空指针
- 2.野指针
- 3.指针数组
- 4.指针作为返回值
- “人针合一”
- 基础
- 中等
入门
笔者对于这个可以算是C语言里较难的部分,其实也无法保证能解析的十分透彻,所以,若有不足之处,请各位师傅不吝赐教,若对于指针有更深理解的大佬,也欢迎讨论。
第一回合
1.指针的作用
指针是一种工具,我们可以通过指针间接访问一块内存,至于为什么要用这个神奇的东西?emmmm,笔者举个栗子,比如小明和小华是两个土豪,然后他们各自有一套房,有一天,他们想换房子住,但是又想走一个比较合理的程序,所以他们交换了房产证,这里的房产证我们就可以看作指针,房产证换了,房子也就换了。这样就可以节省很多麻烦,所以指针可以提高代码效率。
2.我们怎么定义指针?
在笔者最爱的C语言里,指针可以定义整型变量、浮点型变量、字符变量等,也可以定义这样一种特殊的变量,用它存放地址。
在编程语言里。每个变量都有自己的空间,就好比一个班里胖子和瘦子都有,两者占据的空间也是不一样的。在我们没有指针这种概念时,比如我们写一个 int a=10;那么这里的int类型就保存了整型的数据,同理像double或者char也是保存各自所具有的数据类型。
而当我们有了指针这种概念时,那么这就很有意思了,因为任何程序数据跑到内存后,在内存里都有他们的地址,这就是指针。而为了保存一个数据在内存中的地址,我们就需要指针变量。
我们可以这样理解:指针是程序数据在内存中的地址,而指针变量是用来保存这些地址的变量。
3.图解指针可好?
由上图可知,左边已经指入方框内的是指变量的内容,右边指向2002的是变量的地址
笔者用一个十以内的加减法来描写指针的奇妙之处
第一张图是不考虑指针时,第二张是考虑指针时,读者自行比较,就可以发现两者的差别。
4.指针变量的引用
由上图我们不难得出,p与*p,地址和值的关系。
我们来看一个有问题的程序
#include<stdio.h>
int main()
{
int n1,n2;
int *p1,*p2;
*p1=10;
*p2=5;
p1=&n1;
p2=&n2;
printf("n1=%d,n2=%dn",n1,n2);
printf("*p1=%d,*p2=%dn",*p1,*p2);
return 0;
}
运行结果
为什么什么都不出?
是因为指针变量为储存确定的地址时,不能使用,否则系统可能崩溃。
第二回合
有了第一次拜访指针的一定基础后,我们再深入一丢丢,就一小丢丢,放心,不会太难。
1.指针与函数
笔者举个栗子
我们交换两个数,用函数进行交换,代码如下
#include<stdio.h>
void swap1(int x,int y),swap2(int *px,int *py),swap3(int *px,int *py);
int main(void)
{
int a=1,b=2;
int *pa=&a,*pb=&b;
swap1(a,b);
printf("swap1 排序后 a=%d b=%dn",a,b);
a=1;b=2;
swap2(pa,pb);
printf("swap2 排序后 a=%d b=%dn",a,b);
a=1;b=2;
swap3(pa,pb);
printf("swap3 排序后 a=%d b=%dn",a,b);
return 0;
}
void swap1(int x,int y)
{
int t;
t=x;
x=y;
y=t;
}
void swap2(int *px,int *py)
{
int t;
t=*px;
*px=*py;
*py=t;
}
void swap3(int *px,int *py)
{
int *pt;
pt=px;
px=py;
py=pt;
}
运行结果
我们可以由这个黑黑的框框里得出,只有2成功了,为啥?
来笔者每个都分析一下
第一个swap1里面,是最常见的调用方式,也就是值调用,我们的实参a和b对应的传给了形参x和y,那个变量t实现了x和y的交换,当返回到主函数里面时,swap1()中的变量都被系统销毁了,所以主函数里面的值没有任何变化,所以a和b的结果没有改变。
第二个swap2里面,实参是指针变量pa和pb,其地址为变量a和b的地址,当我们用px和py作为形参接受pa和pb的值时,px和py里面就存放了a和b的地址,由第一回合 4.指针变量的引用的那张图可知,px和a代表同一个存储单元,只要我们改变了px的值,这块储存单元里面的内容也会发生改变,我们交换了px和py的值,主函数里面的数也就随机交换了,从而实现了目的。
第三个swap3里面,它在函数里直接交换了px和py,对于这种离谱的行为,我们可以用图来更好的说明,为什么不会影响实参pa和pb。
是时候展示博主的高超绘画功底了!
综上所述,我们要是想用指针来改变主函数里的值,要将该变量的地址或者指向该变量的指针作为实参,在被调用的函数中,用指针类型形参接受该变量的地址,并改变形参所指向变量的值。
2.指针与数组
有人说数组就是指针,可以当成指针来看,其实有些道理,但是两者还是有着很大的区别,笔者曾在https://blog.csdn.net/weixin_52605156/article/details/117828483 中写出了关于指针指向数组的一道题目,我们这里看一下概念。
对于 int shuzu[10],先定义一个长度为10的数组,那么我们可以认为它有十个元素构成,我们对他们的命名是shuzu[0]、shuzu[1]…zhuzu[9]。shuzu[i]可 表示为该数组的第i个元素。(这里我们需要注意的便是shuzu[0]是第一个,而不是shuzu[1])。
在我们之前定义指针的时候,我们比方说int *ps;,那么我们就好比定义了一个整型的数据类型,可以把它看作是指向整型的指针。
当我们把指针和数组相结合的时候,比如我们用ps=&shuzu[0];我们就把它理解为ps这个东西指向数组shuzu的第0个元素,那么ps的值便是数组shuzu[0]的地址。
当我们想调用我们数组里面的内容时,我们可以使用number=*ps;
这时候,我们便把数组shuzu[0]里面的内容给整到了变量number里面。
在实际敲代码的时候,会有指针的移动,也就是现在指向的比如说是第一个元素,然后经过了神奇的黑魔法,他就跑到了第二个元素。其实用大白话说,ps=&shuzu[i];,那么ps+1就是指指向了下一个元素,而ps-1就是指指向了它前一个元素。
所以,经过上述可知,如果ps指向的是shuzu[0],那么*(ps+i)就是指调用的是数组shuzu[i]的内容,ps+i储存的是数组元素shuzu[i]的地址。
当把数组名传递给一个函数时,根本上是传递的是该数组第一个元素的地址。在被调用函数中,该参数是一个局部变量,因此,数组名参数必须是一个指针,也就是一个可以用来储存地址值的变量。
3.指针与指针
这里可以说是一个无厘头的地方,因为这玩意似乎能无限的套下去,指针指向指针指向指针指向… …所以笔者就涉及到指针指向指针,在深层的希望大家自己深挖,也可以在评论区留言讨论。
笔者之前也写过指针指向指针的练习,详情参考博文:
https://blog.csdn.net/weixin_52605156/article/details/117855981
笔者这里直接手撕代码了,就不多讲概念了,因为我发现这个用文字描述太难了,所以直接上代码:
#include <iostream>//csdn-敲代码的xiaolang
using namespace std;
int main()
{
int shuzu[5] = {1, 2, 3, 4, 5};
int *p = shuzu;//我们让指针指向数组
int **zhizhen = &p;//我们再定义一个指针,指向上面的指针
cout << "shuzu = " << shuzu << endl;
cout << "p = " << p << endl;
cout << "&p = " << &p << endl;
cout << "zhizhen = " << zhizhen << endl;
cout << "&zhizhen= " << &zhizhen << endl;
return 0;
}
通过运行可以得到如下的结果:
我们可以看到,shuzu和p的地址是一样的,因为我们用指针指向了数组,又由于zhizhen指向了指针p,所以指针的地址和指针zhizhen的地址是一样的,同理我们如果再用一个指针指向zhizhen,那么它的地址应当和&zhizhen是一样的。
我们再把数组里的元素地址打印出来
添加一段代码:
#include <iostream>//csdn-敲代码的xiaolang
using namespace std;
int main()
{
int shuzu[5] = {1, 2, 3, 4, 5};
int *p = shuzu;//我们让指针指向数组
int **zhizhen = &p;//我们再定义一个指针,指向上面的指针
cout << "shuzu = " << shuzu << endl;
cout << "p = " << p << endl;
cout << "&p = " << &p << endl;
cout << "zhizhen = " << zhizhen << endl;
cout << "&zhizhen= " << &zhizhen << endl;
for (int i = 0; i < 5; i++)
{
cout << "&shuzu[" << i << "] = " << &shuzu[i] << endl;
}
return 0;
}
运行结果:
所以易知指针指向了数组的首地址,也就是shuzu[0]。
4.指针与字符串
字符串可以看作是一个特殊的数组,我们可以用字符串常量对指针进行初始化,或者把字符串存入到数组中,再用指针指向数组。
笔者用代码块来说明:
char *str = "I Love you.";
先对字符指针进行初始化。字符指针指向的是一个字符串常量的首地址,即指向字符串的首地址。
我们再用一个数组表示:
char string[ ]="I Love you.";
string是字符数组,它存放了一个字符串
我们用一个栗子表示:
#include <iostream>//csdn-xiaolang
using namespace std;
int main()
{
char string[] = "csdn";
char *p = string;
cout<< "p = " << p << endl;
cout<<"p = " << (void *) p << endl;//将p强制转换为void *时输出的是地址
cout<< "*p = " << *p << endl;
for(int i = 0 ; i < 4; i++)
{
cout << "&string[" << i << "] = "<< (void *)&string[i] << endl;
}
return 0;
}
运行结果:
我们可以发现,指针p存入了字符串csdn,指针指向的地址和字符串存入的首个字符的地址相同,那么这里需要注意的是:
指针p中存放的是地址,只是当我们使用cout时,如果指针是字符型指针,那么会输出p中地址指向的内存中的内容(这里是c),直到遇到(