一文搞懂,指针常量与常量指针、指针数组与数组指针以及指针函数与函数指针

前言

最近学习C++和C语言关于指针的一些知识,学了指针才发现,什么牛鬼蛇神都出来了,一些常用的知识只要和指针一联合,就搞不懂了,尤其是常用的常量、数组以及函数,因此花了很长时间,做出的一个总结,不足之处,还请各位指教。

正文

首先,需要了解一个优先级顺序:() > [] > * 。这里对了解数组以及函数和指针的关系至关重要。

指针常量与常量指针

指针常量 ---- 指针类型的常量
类型名 * const 指针名 

定义:其实就是一个指针类型的常量,本质上还是一个常量,因此常量p指向的地址就不可以更改(地址是一个常量),也就是说指针的值不可以更改,因为指针的值就是地址。而同一个地址可以有不同的值,因此经过*p解引用之后的值可以更改,因为现在已经不是地址值了。

代码示例: 

int a = 10,b = 20;
int * const p = &a    // 常量必须初始化
p = &b;    // 错误,地址不可更改
*p = 30;   // 正确,值可以更改
常量指针 ---- 常量类型的指针
两种表示方式:
1、const 类型名 * 指针名
2、类型名 const * 指针名 

定义:这里指的是常量的指针,也就是说,本质上是一个指针,这里定义的指针p是指向常量的(值是一个常量),因此*p解引用,其实就是一个常量,不可以进行更改。而同一个值可以有不同的地址,因此p其实是一个指针的值,也就是地址,可以更改。

代码示例: 

int a = 10,b = 20;
const int * p = &a;  / int const *p = &a;    // 常量必须初始化
p = &b;    // 正确,地址可更改
*p = 30;   // 错误,值不可以更改
 注意事项 ---- 指针与字符串常量

 上述中使用的只是简单的数值常量,了解和指针的关系之后,我们可以知道对于指针常量来说,指针经过解引用之后的值是可以进行更改的,因为在内存管理中,数值常量主要存放在数据段,或者堆栈中,因此可以进行修改。

然而,对于字符串常量来说,它被分配在代码段的只读存储区内,通常这块内存区域用于只读,不可对其进行随意修改。因此,虽然在指针数值常量中的数值是可以进行修改的,对于指针字符串常量,str指向的是字符串常量"Hello World"的首地址,而*str就是首个字符"H",然而字符常量是不可以进行修改。另外,指针的地址更是不可以更改,因为这里的指针是一个常量,不可以对地址值修改。

代码示例:

int main()
{
    char * const str = "Hello World";  // 定义了一个指针类型的字符串常量
    * str = "Hello Earth";   // 错误,不可以对字符串的值进行修改
    str = "Hello Earth";   // 错误,不可以对指针的值进行修改
    cout << str << endl;
    
}

指针函数与函数指针

指针函数 ---- 指针类型的函数

 下面代码中声明了两个函数,第一个就是一个普通的函数声明,返回值是一个 int 类型;第二个主要的区别就是加了一个指针 " * " 符号,也就是返回值是一个 int 类型的指针,是一个地址而已。因此,两者也就是用于接收返回值的时候,定义的 int 类型变量有些不同。

类型名 * 函数名 (参数列表) 

1、int fun(int a,int b);
2、int *fun(int a,int b);

定义:一个指针类型的函数,本质上还是一个函数,只是它的返回值是一个地址值,也就是返回一个指针,因此必须使用相同类型名的指针变量接收这个返回值。

代码示例:

struct Data
{
   int m_A;
   int m_B;
};

Data *fun(int &a,int &b)
{
   // 定义Data类型的指针接收指针函数的返回值
   Data *data = new Data;
   data->m_A = a;
   data->m_B = b;
   return data;
}

int main()
{
    // 调用指针函数
    Data * myData = fun(10,20);
    cout<<myData->m_A<<"  "<<myData->m_B<<endl;
    delete myData;
}

注:在调用指针函数时,必须使用一个相同类型的指针来接收这个函数的返回值。 

函数指针 ---- 函数类型的指针
类型名 (*函数名) (参数列表) 

int (*pfun) (int a,int b);

定义:一个函数类型的指针,指针指向的是函数,本质上是一个指针,由前面的优先级可知,()优先级最高,因此函数名与指针 * 结合,定义的也就是一个指针。原理就是当定义一个函数时,此时的函数命就是该函数的首地址,既然函数名也是一个地址,就可以定义一个指针变量来存放。

函数指针使用的时候需要将函数的地址赋给声明的函数指针变量,由于前面提到一个函数名就代表了函数的首地址,因此一般使用直接赋值的方式即可。

// 定义一个函数
int fun (int a, int b)
{
    return  a*b; 
}

声明一个函数指针
int (*pfun) (int a,int b);

pfun = fun;   // pfun是一个存放函数地址的指针,而fun又是定义的一个函数的名称,
              // 本身也是地址,可以直接相等

代码示例:

int Min (int x,int y)
{
     int z;
     if(x > y){
         z = y;
     }
     else{
         z = x;
     }
     return z;
}

int main()
{
    int (*pfun) (int x, int y);
    pfun = Min;   // 都是地址,可以直接相等
    
    int a,b,c;
    cin>>a>>b;
    c = (*pfun)(a,b);   // 调用函数时,可以使用函数指针的方式进行调用,此时解引用出来的是个值,不是地址
    cout<< "Min = "<<c<<endl;

    return 0;
}
注意事项 ---- 函数指针与++、--运算

我们知道,在普通的数值型的指针变量中,指针是可以进行自增和自减,自增(向后加4个字节,32位操作系统中,左右类型指针均占4个字节),自减则相反方向。然而由于函数指针指向的是一个代码段,每个函数的大小是不确定的,因此无法确定递增或递减的步长。也就是下方代码中我们声明的函数指针pfun无法进行++或者--操作,如果需要进行此操作,可以采用定义一个整型变量用来保存每个函数的代号,然后进行函数调用操作。

void func1()
{
   cout << "This is func1." << endl;
}

void func2()
{
    cout << "This is func2." << endl;
}

void func3()
{
    cout << "This is func3." << endl;
}

int main()
{
    void (*pfun)() = func1;  // 声明一个函数指针
    int index = 1;

    // 递增函数指针的索引
    index++;
    // 将函数指针指向相应的函数
    if (index == 1)
    {
        pfunc = func1;
    }
    else if (index == 2)
    {
        pfunc = func2;
    }
    else if (index == 3)
    {
        pfunc = func3;
    }

    // 调用函数指针指向的函数
    pfunc();

    return 0;
}

指针数组与数组指针

指针数组 ---- 指针类型的数组

 指针类型的数组,本质上是一个数组。然而数组中的元素都是指针,其实就是说数组中存放的元素都是地址而已,如果需要操作这个数组,就必须让里面的元素等于地址。

类型名 *数组名 [参数]; 

int *arr [8];  // 这里定义了一个有8个int类型元素的数组,并且数组中的元素都是指针

代码示例: 

int main()
{
	int *pArr[4];
	int Arr[4] = { 1,2,3,4 };
	pArr[0] = &Arr[0];          // 对Arr中的首元素取地址赋给pArr首元素
	cout << pArr[0] << endl;    // 输出地址 0071F8AC
	cout << *pArr[0] << endl;   // 输出值 1
	return 0;
}
数组指针 ---- 数组类型的指针

 本质上是一个指针。并且这个指针指向一个数组,数组中的元素还是数值,但是使用一个指针进行管理。

类型名 (*数组名) [参数]; 

int (*pArr) [8];  // 声明了一个指针,并且这个指针指向了一个具有8个int型元素的数组

下例中,声明一个指针指向 int [4] 类型。然后,我们将 Arr 的地址给 pArr,这样 pArr 就指向了数组的第一行。接着,我们使用 ++pArr 将 pArr 指向数组的下一行,然后使用 **(++pArr) 来访问这一行的第一个元素,输出结果为 5。

代码示例: 

int main()
{	
	int Arr[3][4] = {
		{ 1, 2, 3, 4 },
		{ 5, 6, 7, 8 },
		{ 9, 10, 11, 12 }
	};
	int (*pArr)[4];  
	pArr = Arr;
 	cout << **(++pArr)<< endl;   // 输出 5
	system("pause");
	return 0;
}

总结

定义不同

指针常量本质是一个常量,地址是一个常量,地址不可改(&指向不可改),值可以改。
常量指针本质是一个指针,值是一个常量,值不可改,地址可以改(&指向可以改)。

指针函数本质是一个函数,但是返回值是指针类型,使用时,必须定义同类型指针变量接收。
函数指针本质是一个指针,声明一个函数指针,可以为其赋给其他函数地址。使用(*fun)调用函数

指针数组本质是一个数组,数组中的元素是指针,占有多个指针的存储空间。
数组指针本质是一个指针,指针管理数组的元素,占有内存中一个指针的存储空间。

如果想简单一点辨别,也可以看出我写的时候,凡是本质上是指针的,名称都是指针在后,本质上是其本身的名称都是指针在前。

写法不同

指针常量:int * const p = &a;

常量指针:const int * p = &a; / int const * p = &a;

指针函数:int * fun(int a,int b);

函数指针:int (*pfun) (int a,int b); 

指针数组:int *Arr[10]; 

数组指针:int (*pArr)[10];

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

一伦明悦

感谢,您的支持是我最大的动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值