( •̀ ω •́ )✧本文章将会对指针进行一个初步讲解
如果有问题欢迎大佬指出!如果本文章有侵权会立即删除!!
参考文章:C++ 指针 | 菜鸟教程 (runoob.com)
【C++学习笔记】什么是野指针?如何避免它的出现? - 知乎 (zhihu.com)
C++ 涨知识!new和delete知识总结(全面详细)_c++ delete用法_WhiteShirtI的博客-CSDN博客
以及书籍《信息学奥赛一本通》《Dev-C++基础教程》
本文章中的程序我都有测试过一遍,有试过毒,请放心食用OwO
话不多说,马上开始!
地址
每一个变量都有一个内存位置,每一个内存位置都定义了可使用(&)取地址运算符访问的地址,它表示了在内存中的一个地址
如果把一个变量比作房间,那么地址就相当于这个房间的门牌号(生动の比喻)
对于一个变量,我们可以这样输出它的值和地址
#include<iostream>
using namespace std;
int main()
{
int a=10;
cout<<"a的值为"<<a<<endl;
cout<<"a的地址为"<<&a<<endl;
//&为取地址操作符,这里是输出a的地址
return 0;
}
输出结果:
a的值为10
a的地址为0xca805ff8cc
注意,&a属于一个表达式,不能为&a=&b,不然会出现以下错误:
lvalue required as left operand of assignment表达式或常量不能作为左值
指针变量
学习完地址,接下来让我们正式进入主题——指针
指针是一个变量,其值为另一个变量的地址,定义(声明)指针的一般形式为:
类型说明符 *变量名;
比如,要定义一个整型的指针变量,就是:
int *p;
当然,如果你想。你可以以定义浮点型(float),双浮点型(double),字符型(char)等类型的指针变量,仅仅只是在数据类型稍作改变即可!
下一步便是去了解下指针与普通变量的对应关系
这里以int *p与int a举例子:
p | &a |
*p | a |
*p=3 | a=3 |
(同一行的值是相等的!)
为什么*p相当于a呢?我把*看作访问xx地址下的值的
p这个指针变量的值是一个地址,那么前面加个*也就表明了我想要访问该地址下的值
但是注意,不是*和变量在一起就是指向地址的值,我们令变量为p,现在有两种情况:
情况一——变量p不为指针变量:那么这边的“*”就会被当成运算符使用
情况二——是指针变量(非多重指针,指向的是其他数据类型变量的地址),同时“*”前面有数据类型:数据类型遇到“*”会先抢走,变成指向相应数据类型的指针类型,然后p就是个孤独的指针变量了,这个时候p指向的是地址
接下来,我们试着用程序输出p所指向的地址及其值康康:
#include<iostream>
using namespace std;
int main()
{
int a=10;
cout<<"a的值为"<<a<<endl;
cout<<"a的地址为"<<&a<<endl;//&为取地址操作符,这里是输出a的地址
int *p=&a;//将a的地址传给p
cout<<"p所指向的地址中的值:"<<*p<<endl;
cout<<"p的值为"<<p<<endl;
return 0;
}
输出结果:
a的值为10
a的地址为0x42aedff6f4
p所指向的地址中的值:10
p的值为0x42aedff6f4
接下来,介绍两个特别的名字:空指针和野指针
先来说说什么是空指针吧~~~
空指针
空指针指向内存地址 0 ,一般被用于初始化,内存地址0有特别重要的意义,它表明该指针不指向一个可访问的内存位置。
但按照惯例,如果指针包含空值(零值),则假定它不指向任何东西。空指针在概念上不同于未初始化的指针。它可以确保不指向任何对象或函数;而未初始化的指针则可能指向任何地方(这个就是下面我们要说的野指针)。在编程时,建议先把声明的指针先进行初始化,初始化为空指针
NULL初始化
当使用NULL初始化时,此时p为空指针
NULL实际上就是内存地址 0,初始化的方法如下:
int *p=NULL;
还可以这样:
int *p=0;
这时可能就有读者好奇了:既然是空指针,如果不指向任何一个可访问的内存位置,那么我们用程序输出会得到什么呢?
说干就干:
#include<iostream>
using namespace std;
int main()
{
int *a=NULL;
cout<<"a的地址为"<<a<<endl;
cout<<"a的值为"<<*a<<endl;
return 0;
}
输出结果:
a的地址为0
a的值为
程序到这里就戛然而止了(而不是我不填👀),因为在大多数的操作系统上,程序不允许访问地址为 0 的内存,该内存是操作系统保留的。`(*>﹏<*)′
既然说到了初始化,那么来讲讲其他初始化的方法:
使用new初始化
一共有两种:
类型指针 指针变量名 = new 类型
类型指针 指针变量名 = new 类型(初始值)
这里的数据类型是否加括号可以看个人习惯
注意,使用new申请的空间记得要使用delete释放掉哦
delete 指针变量名
如果是数组的话可以试试这样(new会申请连续的空间):
类型指针 指针变量名 = new 类型[元素个数]
delete []指针变量名
同样的,挂一个程序方便各位更好理解:
#include<iostream>
using namespace std;
int main()
{
int *a=new(int)(10);
cout<<"a的地址为"<<a<<endl;
cout<<"a的值为"<<*a<<endl;
delete a;
//申请空间但是值不确定
int *p=new int;
/*int *p=new(int)*/
//申请空间并初始化,值为10
int *p2=new int(10);
//为数组申请空间,空间大小为4*10=40
int *arr=new int[10];
//释放空间
delete p;
delete p2;
//适用于数组
delete []arr;
return 0;
}
使用变量的地址初始化
这个也很方便理解,当前面有现成的地址时,可以使用它来初始化,这里举一个例子:
int *p=&a;//将a的地址传给p
野指针
如果声明而不进行初始化的指针变量进行使用,那么就是相当于抽随机大礼包(因为它可能指向任何地方)
#include<iostream>
using namespace std;
int main()
{
int *a; //野指针
cout<<"a的地址为"<<a<<endl;
cout<<"a的值为"<<*a<<endl;
return 0;
}
此时a的值与地址不明确,如果在实践中使用会出问题,手动初始化是一个好习惯(再次提醒)
无类型指针
无类型指针的定义方法其实类似,只不过它的数据类型为void:
void *变量名;
为什么要将无类型指针拉出来特别讲呢?因为他灵活呀【滑稽】
它可以根据不同情况指向不同类型的值,我们可以不明确它的定义,然后运用强制类型转化的方法去明确其类型,现在来举个例子吧(●ˇ∀ˇ●):
#include<iostream>
using namespace std;
int main()
{
int a=1;
double b=3;
void *p=0;//无类型指针
//无类型指针也可以使用初始化哦OwO
p=&a;//将a的地址传给p
cout<<"a的地址为:"<<(int *)p<<endl;
cout<<"a="<<*(int *)p<<endl;
p=&b;
cout<<"b的地址为:"<<(double *)p<<endl;
cout<<"b="<<*(double *)p<<endl;
return 0;
}
输出结果
a的地址为:0xdf831ffd24
a=1
b的地址为:0xdf831ffd18
b=3
(如果对这里强制转化感到疑惑的读者可以先去了解下普通变量的强制转化)
既然能够强制转化,是不是意味着我们可以为所欲为?(指明明是指向某类型变量的指针强制转化为其他类型)
#include<iostream>
using namespace std;
int main()
{
int a=1;
double b=3;
void *p=0;//无类型指针
//无类型指针也可以使用初始化哦OwO
p=&a;//将a的地址传给p
cout<<"a的地址为:"<<(int *)p<<endl;
cout<<"a="<<*(int *)p<<endl;
p=&b;
cout<<"b的地址为:"<<(double *)p<<endl;
cout<<"b="<<*(long long *)p<<endl;
return 0;
}
输出结果:
a的地址为:0xd8e05ffe64
a=1
b的地址为:0xd8e05ffe58
b=4613937818241073152
结论:可以是可以,但是最后的值是混乱的(
多重指针
这个指针,我愿称之曰:套娃指针
在C++中,既然有指向数据类型的指针,那么必定就有指向指针的指针啦
它的定义方式如下(先给大家看两个*的):
类型说明符 **变量名;
假如我要定义一个指向(int *)指针变量的指针,那么我定义的时候就要多一个*
也就是
int ** p2;
还记得我们前面讲过的吗?如图:
这里补充一点,如果是多重指针,不论有多少“*”,都会被数据类型抢走成为新的指针类型
我举个例子给大家吧(套娃警告)
#include<iostream>
using namespace std;
int main()
{
int x=0;
int *p1=&x;
cout<<"p1所指向的地址为:"<<p1<<endl;
cout<<"p1所指向的地址的值为"<<*p1<<endl;
int **p2=&p1;
cout<<"p2所指向的地址为:"<<p2<<endl;
cout<<"p2所指向的指针所指向的地址为"<<*p2<<endl;
cout<<"p2所指向的指针所指向的地址的值为"<<**p2<<endl;
return 0;
}
输出结果:
p1所指向的地址为:0xd0c21ff704
p1所指向的地址的值为0
p2所指向的地址为:0xd0c21ff6f8
p2所指向的指针所指向的地址为0xd0c21ff704
p2所指向的指针所指向的地址的值为0
看完上面的例子,你是不是有点晕晕的(
我们先看看一下表格:
x | *p1 | **p2 |
&x | p1 | *p2 |
(-) | &p1 | p2 |
那么**p2怎么理解?我们这个样子拆开来看
* * p2
首先先单独看个p2,p2指向p1这个指针变量的地址,第二个*和p2合在一起表示我想要访问这个地址下的值,恰好这个p1又是个指针变量,这个值也是个地址(且这个地址指向x)
然后第一个*,第二个*和p2合在一起,也就相当于*p1了,p1的值为x的地址,加个*表示我想要访问这个地址下的值,也就是x的值
也就是间接访问数据(
相信大家看到这里都对指针有了初步了解了,下期将会讲关于指针与数组,字符串等应用!