[C/C++]指针,指针数组,数组指针,函数指针

news2024/12/24 3:21:12

文章目录

      • 指针
        • 内存空间的访问方式
        • 指针变量的声明
        • 指针的赋值
        • 指针运算
        • 用指针处理数组元素
        • 指针数组
        • 用指针作为函数的参数
        • 指针型函数
        • 指向函数的指针

指针

指针是C++从C中继承过来的重要数据类型。通过指针技术可以描述各种复杂的数据结构,可以更加灵活的处理字符串,更方便的处理数组,并支持动态内存分配,提供了函数的地址调用和自由的在函数之间传递各种类型的数据等。这对系统软件的设计是必不可少的。同时,指针也是C++的难点之一,为了理解指针,先要理解内存地址的概念。

内存空间的访问方式

在计算机中,所有的程序和数据都是存放在存储器的内存中.内存的基本单位是字节(Byte),1个字节由8位二进制位组成。一般把存储器中的1字节称为1个内存单元,不同的数据类型所占的内存单元数不等。为了正确地访问内存单元,必须给每个内存单元确定一个整形编号,该编号称为该内存单元的地址。计算机就是通过这种地址编号的方式来确保内存数据读写的准确定位的。
程序中每个变量在内存中都会有固定的位置,有具体的地址。由于变量的数据类型不同,它所占的内存单元数也不同。若我们在程序中做定义为:

int a;
double m;
char ch;

一般的编译系统为变量分配内存:变量a是基本整型类型,在内存中占4个字节;m是双精度实型,占8个字节;ch1是字符型,占用1字节。变量的值存放在内存单元中,这些内存单元中存放的数据就是变量本身的值。

对内存中变量的访问,有两种方式:

一是通过变量名直接访问变量的值,这种访问称为直接访问,如

a = 1

通过变量名a直接把1赋值到a所占用的内存单元中。而把某个变量内存变量a对应的内存单元的地址(简称变量a的地址,假设为2A00H)放在另一个变量pa对应的内存单元中,如果对变量pa做直接访问,如

cout<<pa;

得到的是pa内存单元中的数据,即输出的是变量a的地址;如果取得这个地址后,按照该地址的指示,再找到变量a所对应的内存单元中的数据(变量a的值为1),这种通过pa访问变量a的内存单元的访问方式就是间接访问。如图1所示:
20230117_2

这里变量的地址又称为变量的指针,严格地说,一个指针就是一个地址。如果用变量来存放这些地址,这样的变量就是指针变量,而一个指针变量却可以被赋予不同的指针值(即变量)。如果一个指针变量所占的内存单元中存储了某个变量的地址,我们就称指针变量指向该变量,这种指向是通过该指针变量存储的地址体现的。指针变量的值不仅可以是变量的地址,也可以是其他数据结构的地址。比如在一个指针变量中可以存放一个数组或一个函数的首地址。
在一个指针变量中存入一个数组或一个函数的首地址有何意义呢?因为数组或函数都是连续存放的,通过访问指针变量取得了数组或函数的首地址,也就找到了该数组和函数。这样一来,凡是出现数组,函数的地方都可以用一个指针变量来表示,只要该指针变量中被赋予数组或函数的首地址即可。这样会使程序的概念十分清楚,程序本身也十分精炼,高效。在C++语言中,一种数据类型或数据结构往往都占有一组连续的内存单元。用"地址"这个概念并不能很好地描述一种数据类型或者数据结构,而"指针"虽然实际上也是一个地址,但它却可以是某个数据结构的首地址,它是"指向"一个数据结构的,因为概念更为清晰,表示更为明确。这也是引入"指针"概念的一个重要原因。

指针变量的声明

指针变量与其他的变量一样,在使用前必须加以声明。其声明方式与一般变量声明相比,只是在变量名前面多加一个星号(*)。

指针变量声明的一般形式为:

类型说明符 *变量名;

其中,(*)表示这是一个指针变量;变量名即为声明的指针变量名,命名规则和普通变量名相同,遵循标识符规则;类型说明符表示本指针变量所指向的对象(变量,数组或函数等)的数据类型,这说明指针所指向的内存单元可以用于存放什么类型的数据,我们称之为指针的类型 ,而所有指针本身的类型都默认是

unsigned long int

例如:

int *ptr1;

声明ptr1(而不是*ptr1)是一个指向整型变量的指针变量,它的值是某个整型变量的地址。至于ptr1究竟指向哪一个整形数据是由ptr1所赋予的地址所决定的。

float *ptr2;
char *ptr3;

声明ptr2是指向单精度变量的指针变量;ptr3是指向字符型变量的指针变量。
应该注意的是,一个指针变量只能指向同类型的变量,如ptr2只能指向单精度变量,不能时而指向一个单精度变量,时而指向一个整型变量。
在C++中没有一种孤立的"地址"类型。声明指针变量时必须要指明它的类型,即它是用于存放什么类型的地址的,这是因为指针变量的运算规则与它所指的对象类型是密切相关的。当指针变量被赋值后,该指针变量以及它所指向的变量所能进行的运算以及运算规则是由声明时的类型来决定的。

指针的赋值

声明了一个指针,只是得到了一个用于存储地址的指针变量,变量中并没有确定的值,其中的地址仍然是一个随机数,即不能确定此时该指针变量中存储的是哪个内存单元的地址,而该内存单元中可能存放着重要的数据或程序代码,若果随便更改其中的数据会造成系统故障。因此声明指针后一定要要先为其赋值,是该指针有正确的指向,然后才能通过它来使用它所指向的内存单元中的数据。与其他普通变量不一样,指针变量的赋值有两种:

(1) 在声明指针变量的时候进行初始化赋值

语法格式为:

数据类型 *指针变量名 = 初始地址;

例如:

int a,*pa = &a;

上例声明了整型的变量a,整形的指针变量pa,并且给pa赋初始值为a的地址,注意,并不是把a的地址赋给*pa

(2) 声明之后,再用赋值语句为其赋值

语法格式为:

指针变量名 = 地址;

例如:

int a,*pa;
pa = &a;

用这种方法,被赋值的指针变量前不能加’*'说明符,如写

*p = &a;

是错误的。
指针变量存放另一个同类型的变量的地址,因而不允许将任何非地址类型的数据赋值给它。如"p = 2000;"就属于非法,这也是一种不能转换的错误,因为"2000"是整型常量(int),而p是指针变量(int*).
由于在C++语言中,变量的地址是由编译系统分配的,对用户完全透明,因此必须使用地址运算符’&'来取得变量的地址。
除了用变量的地址给指针变量赋值以外,我们还可以把一个指针变量的值赋予指向相同数据类型的另一个指针变量,把数组的首地址赋值给指向数组的指针变量,把函数的入口地址赋给指向函数的指针变量等等。例如:

int a,b[5],*pa,*pb,*pc;
pa = &a;
pb = pa;    //将指针变量pa的值赋给相同类型的指针变量pb
pc = b;     //将数组名(是一个数组的首地址)直接赋值给一个相同类型的指针变量pc
int (*pf)(int,int);
pf = f; //f为函数名,此函数为两个整形形参,返回值为整形。

对指针变量还可以赋空值,即使该指针变量不指向任何具体的变量,这和指针变量不赋值是不同的。指针变量不赋值时,值是任意的,即非法地址,是不能用的,否则将造成意外错误。例如:

#define NULL 0
int *p = NULL;

或者

int *p = 0; //p不指向任何变量

一般情况下,指针的值只能赋给相同类型的指针。但是void类型指针,可以存储任何类型的对象地址,即任何类型的指针都可以赋值给void类型的指针变量(注意void类型指针一般只在指针所指向的数据类型不确定时使用)。例如:

void *pv;
int i;
pv = &i;

指针运算

指针是一种数据类型,与其他数据类型一样可以参与部分运算。

指针变量可以进行的运算主要有以下几种:

1.取内容运算

指针变量的取内容运算也称为指针变量的引用。引用形式为:

*指针变量;

其中*是取内容运算符,是单目运算符,其结合性是为右结合,用来表示指针变量所指向的数据对象。
*运算符之后跟的变量必须是指针变量。
需要注意的是指针运算符*和指针变量说明符*不是一回事。在指针变量说明中,*是类型说明符,表示其后的变量是指针类型;而表达式中出现的*则是一个运算符,用来表示指针变量所指向的数据对象。
事实上,若定义了变量以及指向该变量的指针为:

int a,*p;

若p = &a; 则称p是指向变量a,或者说p具有了变量a的地址。
在以后的程序处理中,凡是可以写&a的地方,都可以替换成表示指针的p,a也可以替换成*p
case 1:指针变量的基本使用

#include <iostream>

using namespace std;

int main(void)
{
	int a,b,*p;
	a = 10;
	p = &a;
	cout<<"a = "<<a<<endl;
	cout<<"*p = "<<*p<<endl;
	p = &b;
	*p = 20;
	cout<<"b = "<<b<<endl;
	cout<<"*p = "<<*p<<endl;
	
	return 0;
}

运行结果如下:
20230117_3

2.加减运算
指针变量的加减运算只能对指向数组的指针变量进行,对指向其他类型的指针变量做加减运算是没有意义的。

假设pa为指向数组a的指针变量,则pa+n,pa-n,pa++,++pa,pa–,–pa运算都是合法的。指针变量加或减一个整数n的意义是把指针指向的当前位置(指向某数组元素)向前或向后移动n个位置。应该注意的是:数组指针变量向前或向后移动一个位置,和地址+1或-1在概念上是不一样的。因为数组可以是不同类型的,各种类型的数组元素所占的字节长度是不同的。
例如:

int a[5],*pa = a;
pa += 2;    //pa = a数组的起始地址+2*4字节,int类型的数据占据4个字节

只有指向同一数组的两个指针之间相减才有意义。两指针变量相减所得之差是两个指针所指元素之间相差的元素个数。实际上是两个指针值(地址)相减之差再除以该数组元素的长度(占字节数)。很显然两个指针变量相加是没有意义的。

3.关系运算

两个同类型的指针变量可以进行关系运算,此时这两个指针变量一般指向同一数组,进行关系运算可以表示它们所代表的地址之间的关系。
例如:

p1 == p2;   //若成立,则表示p1和p2指向同一元素
p2 > p1;    //若成立,则表示p2处于高地址位置
p2 < p1;    //若成立,则表示p2处于低地址位置

从键盘输入两个整数,按由小到大的顺序输出

#include <iostream>
using namespace std;

int main()
{
	int num1,num2;
	int *num1_p = &num1,*num2_p = &num2,*pointer;
	cout<<"Input the first number:";
	cin>>*num1_p;
	cout<<"Input the second number:";
	cin>>*num2_p;
	cout<<"num1 = "<<num1<<",num2 = "<<num2<<endl;
	if(*num1_p > *num2_p){
		pointer = num1_p;
		num1_p = num2_p;
		num2_p = pointer;
	}
	cout<<"min = "<<*num1_p<<",max = "<<*num2_p<<endl;
	
	return 0;
} 

运行结果如下:
20230117_4
本例处理的思路是:
交换指针变量num1_p和num2_p的值,而不是变量num1和num2的值(num1和num2并未交换,仍保留原值),最后通过指针变量输出结果。程序在运行过程中,实际存放在内存中的数据没有移动,而是将指向该变量的指针交换了指向。如下图所示
20230117_5
当指针交换指向后,num1_p和num2_p由原来指向的变量num1和num2改变为指向变量num2和num1,这样一来,*num1_p就表示变量num2,*num2_p就表示num1

用指针处理数组元素

由于指针加减运算的特点使得指针特别适合用于处理一段连续内存空间里的同类型数据,而数组恰好是一组同类型数据的集合。

当一个数组被定义后,程序会按照其类型和长度在内存在内存中为数组分配一段连续的地址空间,数组中的元素依"行序优先"存储在这段连续的内存空间中,数组名就是这块连续内存单元的首地址。因此在程序中用指针来依次处理数组中的元素是极其方便快捷的。

一个数组是由各个数组元素(下表变量)组成的。每个数组元素按其类型不同占有若干个连续的内存单元。

一个数组元素的首地址是指它所占有的几个内存单元的首地址,使其指向变量,当然也可以存放用指针变量来存储数组的首地址或数组元素的地址。也就是说,指针变量可以指向数组和数组变量,对数组而言,数组和数组元素的引用,也可以同样使用指针变量。

1.指针与一维数组
程序中如果定义了一个一维数组,系统会为该数组在内存中分配一段连续的存储空间,数组名代表该数组所占内存段的首地址,也就是数组的指针。 数组的元素按下标由小到大存储在这段内存空间中,每个元素占用几个连续的内存空间,都有各自的地址。而下标为0的这个元素由于存储在这段内存空间的最前面,因此该元素的地址也是这段内存空间的首地址。程序中可以定义一个和数组元素同类型的指针变量,并将数组的首地址传给指针变量,则该指针变量就指向了这个一维数组,即指向下标为0的这个元素。例如:

int a[10],*ptr; //定义数组与指针变量
ptr = a;    //或者: ptr = &a[0];

则ptr就得到了数组的首地址。其中,a是数组的首地址,&a[0]是数组元素a[0]的地址,由于a[0]的地址就是数组的首地址,所以两条赋值操作效果完全相同。指针变量ptr就是指向数组a的指针变量。如下图所示;
20230117_6

若ptr指向一维数组,则在C++中规定指针对数组的表示方法
(1) ptr+n 与 a+n 表示数组元素a[n]的地址,即&a[n].对整个a数组来说,共有10个元素,n的取值为09,则数组元素的地址就可以表示为:ptr+0ptr+9或者a+0a+9,与&a[0]+0&a[0]+9保持一致
(2) 知道了数组元素的地址表示方法,*(ptr+n)*(a+n)就表示为数组的各元素,即等效于a[n];
(3) 指向数组的指针变量也可以用于数组的下标形式表示ptr[n],其效果相当于*(ptr+n)

根据以上叙述,对一维数组的引用,既可以使用传统的数组元素下标法,也可以使用指针的表示方法。

case 2:输入输出一维数组各个元素

/*方法一:下标法*/
#include <iostream>
using namespace std;

int main(void)
{
	int a[10];
	for(int i=0;i<=9;i++){
		cin>>a[i];
	}
	for(int i=0;i<=9;i++){
		cout<<a[i];
	}
	
	return 0;
}
/*方法二:数组名法*/
#include <iostream>
using namespace std;

int main(void)
{
	int a[10];
	for(int i=0;i<=9;i++){
		cin>>*(a+i);
	}
	for(int i=0;i<=9;i++){
		cout<<*(a+i)<<' ';
	}
	cout<<endl;
	return 0;
}
/*方法三:指针法*/
#include <iostream>
using namespace std;

int main(void)
{
	int a[10],*ptr = a;
	for(int i=0;i<=9;i++){
		cin>>*(ptr+i);
	}
	for(int i=0;i<=9;i++){
		cout<<" "<<*(ptr+i);
	}
	cout<<endl;
	return 0;
}
//或者
#include <iostream>
using namespace std;

int main(void)
{
	int a[10],*ptr;
	for(ptr=a;ptr<=a+9;ptr++){
		cin>>*ptr;
	}
	for(ptr = a;ptr<=a+9;ptr++){
		cout<<*ptr<<' ';
	}
	return 0;
}

2.指针与多维数组

用指针变量可以指向一维数组,也可以指向二维数组或多维数组。这里以二维数组为例介绍指向多维数组的指针变量。

(1) 二维数组的地址

定义一个二维数组:

int a[3][4] = {{2,4,6,8},{10,12,14,16},{18,20,22,24}};

表示二维数组有三行四列共12个元素,在内存中按行存放,存放形式如下图:
20230118_1
在C++中我们可以把二维数组a看出一个特殊的一维数组,它包含三个元素,分别为a[0],a[1],a[2],各个元素又是一个有4个元素的一维数组,即a[0],是由a[0][0],a[0][1],a[0][2],a[0][3]共4个元素构成的一维数组,a[0]相当于一维数组的名字。
a是一个数组名,代表该数组所占内存段的起始地址,即二维数组首元素a[0]的地址。而a[0]是由4个整形元素组成的一维数组,因此a代表的是首行(第0行)的首地址。a+1代表二维数组元素a[1]的地址,即第1行的首地址。如果二维数组首行的首地址为2000,则a+1为2016(2000+4*4B).a+2代表的是二维数组元素a[2]的地址,即第2行的首地址。而&a[i]与a+i等价,因此它们都是二维数组元素a[i]的地址,即第i行的首地址。
a[0],a[1],a[2]既然都是一维数组名,而C++中又规定数组名代表数组首元素地址,因此a[0]代表一维数组a[0]中第0列元素的地址,即&a[0][0];a[1]代表一维数组a[1]中第0列元素的地址,即&a[1][0];a[2]代表一维数组a[2]中第0列的地址,即&a[2][0].
由于a[i]是一维数组a[i]中第0列元素的地址,则a[i]+1就是就是一维数组a[i]中第1列元素的地址,即&a[i][1];a[i]+2就是一维数组a[i]中第二列元素的地址,即&a[i][2];a[i]+3就是一维数组a[i]中第三列元素的地址,即&a[i][3];
在一维数组的指针中讲述过, a[i]*(a+i)等价。因此 a[i]+1*(a+i)+1的值都是&a[i][1]; a[i]+2*(a+i)+2的值都是&a[i][2]; a[i]+3*(a+i)+3的值都是&a[i][3];既然 a[i]+j*(a+i)+j都是a[i][j]的地址,那么 *(a[i]+j) 和 *(*(a+i)+j)都是a[i][j]的值。
case 3:二维数组地址和元素的多种表示方式

/*二维数组地址和元素的多种表示方式*/
#include <iostream>

using namespace std;

int main(void)
{
	int a[3][4] = {{2,4,6,8},{10,12,14,16},{18,20,22,24}};
	cout<<a<<" "<<*a<<" "<<a[0]<<" "<<&a[0]<<" "<<&a[0][0]<<" ";
	cout<<a[0][0]<<endl;
	cout<<a+1<<" "<<*(a+1)<<" "<<a[1]<<" "<<&a[1]<<" "<<&a[1][0]<<" ";
	cout<<a[1][0]<<endl;
	cout<<a+2<<" "<<*(a+2)<<" "<<a[2]<<" "<<&a[2]<<" "<<&a[2][0]<<" ";
	cout<<a[2][0]<<endl;
	
	return 0;
}

20230118_2

(2)指向二维数组元素的指针变量

/*用指针变量输入输出二维数组元素的值*/
#include <iostream>
using namespace std;

int main(void)
{
	int a[3][4],*ptr;
	ptr = a[0];
	for(int i=0;i<3;i++){
		for(int j=0;j<4;j++){
			cin>>*ptr++;
		}
	}
	ptr = a[0];
	for(int i=0;i<3;i++){
		for(int j=0;j<4;j++){
			cout<<" "<<*ptr++;
		}
		cout<<endl;
	}
	
	return 0;
}

说明:在程序中定义了和数组元素同类型的指针变量ptr,可以用来存储数组元素的地址。执行第一个双层for循环前首先为其赋第0行的首地址,即第0行第0列这个元素的地址,使其指向a[0][0].循环体中 cin>>*ptr++所起的作用为先输入值给ptr所指向的变量(即先执行 *ptr),然后ptr+1,指向下一个数组元素。通过该双层for循环,使ptr按a数组元素在内存中的排放顺序依次取到每个数组元素,为每个元素赋值。第一个双层for循环结束后,指针变量指向数组的尾部的后面。因此在执行第二个双层for循环之前,需要重新为其赋第0行第0列的地址,通过双层for循环和ptr指针依次输出每个元素的值。

(3)指向一维数组的指针变量

声明形式为:

类型说明符 (*指针变量名)[长度];

其中"类型说明符"为所指向数组的数据类型。 *表示其后的变量是指针类型。“长度"表示该指针变量所指的一维数组包含的元素个数,即一维数组的长度。需要注意的是”(*指针变量名)"中的括号不可少,否则表示的是指针数组,其意义就完全不同了。

/*输出二维数组元素的值*/
#include <iostream>
using namespace std;

int main(void)
{
	int a[3][4] = {{2,4,6,8},{10,12,14,16},{18,20,22,24}};
	int (*ptr)[4];	//定义指向一维数组的指针变量
	ptr = a;
	for(int i=0;i<3;i++){
		for(int j=0;j<4;j++){
			cout<<" "<<*(*(ptr+i)+j);
		}
		cout<<endl;
	} 
	
	return 0;
}

说明:在程序中定义了指针变量ptr,它指向包含4个整形元素的一维数组。此时ptr的增值是以它所指向的一维数组的长度为单位,即ptr加1是指指向下一个一维数组。即向后移动16字节(4*4B = 16B).执行for循环前首先将其赋值为a,即第0行的首地址,循环体中ptr+i是二维数组第i行的首地址,*(ptr+i)+j是a数组第i行第j列元素的地址,*(*(ptr+i)+j)是a[i][j]的值。

指针数组

如果一个数组的每个元素都是指针变量,这个数组就是指针数组。指针数组的每个元素都必须是同一类型的指针,并存放同类型的地址。
指针数组的声明形式:

数据类型 *数组名[数组长度]

其中数组长度指出数组元素的个数,数据类型确定每个数组元素指针的类型,数组名是指针数组的名称,也是这个数组所占内存段的首地址。
例如:

int *pt[4];

由于 [] 比 *优先级高,所以首先是数组形式pt[4],表明这是一个包含4个元素的一维数组。然后才是与 *结合,说明该数组的每个元素都是指针,int表示数组元素存放的都是整形的地址,用来指向整形的数据。数组名pt是该数组的指针,即该数组所占内存段的首地址。
在使用中注意 int *pt[4]int (*pt)[4]的区别,前者表示一个数组元素都是指针的数组,后者是一个指向数组的指针变量。
由于指针数组的每个元素都是指针,在使用前应该先赋值,使这些元素有所指向,然后才能使用它们,即通过这些数组元素间接访问它们所指向的内存单元。
通常可以使用一个指针数组来指向一个二维数组,指针数组中的每个元素都被赋予二维数组每一行的首地址。使用指针数组,对于处理不定长字符串更为方便,直观。
通过下例来加深我们对其概念的理解。

#include <iostream>
using namespace std;

int main(void)
{
	int a[3] = {1,2,3};
	int b[3][4] = {{2,4,6,8},{10,12,14,16},{18,20,22,24}};
	int *pt[3];
	for(int i=0;i<3;i++){
		pt[i] = &a[i];
	}
	cout<<"array a:";
	for(int i=0;i<3;i++){
		cout<<*pt[i]<<' ';
	}
	cout<<endl;
	for(int i=0;i<3;i++){
		pt[i] = b[i];
	}
	cout<<"array b:"<<endl;
	for(int i=0;i<3;i++){
		for(int j=0;j<4;j++){
			cout<<pt[i][j]<<" ";
		}
		cout<<endl;
	}
	
	return 0;
}

说明:在程序中定义了指针数组pt,第一个for循环为pt[i]赋值为元素a[i]的地址,使pt[i]指向a[i],因此第二个for循环中输出 *pt[i],就是输出pt[i]所指向的a[i]的值。第三个for循环为pt[i]重新赋值为b[i]的地址即b数组第i行第0列的地址,pt[i][j]与 *(p[i]+j)等价,相当于 *(b[i]+j),都是取b[i][j]的值。

用指针作为函数的参数

当不同的函数之间需要传送大量数据时,程序执行时调用函数的开销就会比较大。此时如果将需要传递的数据存放在一段连续的内存空间中,就可以只传递这批数据的起始地址,而不必传递数据的值,从而减小开销,提高程序执行的效率。C++函数的参数不仅可以是基本数据类型的变量,对象名,数组名,也可以是指针类型。使用指针类型作为函数参数,实际是向函数传递变量的地址。变量的地址在调用函数时作为实参,被调函数使用指针变量作为形参接收传递的地址。这里实参的数据类型要与形参的指针所指向的对象数据类型一致。由于被调函数中获得了所传递变量的地址,在该地址空间的数据当被调函数调用结束后被"物理"地保留下来。
需要注意的是,C++语言中实参和形参之间的数据传递是单向的"值传递"方式,指针变量作为函数参数也要遵循这一规则。因此不能企图通过改变指针形参的值来改变指针实参的值,但可以改变实参指针变量所指变量的值。我们知道函数的调用可以并且只可以得到一个返回值,而运用指针变量做参数,可以得到多个变化了的值,这就是运用指针变量的作用。

#include <iostream>
using namespace std;

void swap(int *x,int *y)
{
	int t;
	t = *x;
	*x = *y;
	*y = t;
}
int main(void)
{
	int a = 3,b = 4;
	cout<<"a = "<<a<<" b = "<<b<<endl;
	swap(&a,&b);
	cout<<"a = "<<a<<" b = "<<b<<endl;
	
	return 0;
}

运行结果如下:
20230119_1
若以数组名作为函数参数,数组名就是数组的首地址,实参向形参传递数组名实际上就是传送数组的地址,形参得到该地址后也指向同一数组。实参数组和形参数组各元素之间并不存在"值传递",在函数调用前形参数组并不占用内存空间,在函数调用时,形参数组并不另外分配新的存储单元,而是以实参数组的首地址作为形参数组的首地址,这样实参数组与形参数组共同占用一段内存。如果在函数调用过程中使形参数组的元素值发生变化,实际上也就使实参数组的元素值发生了变化。函数调用结束后,实参数组各元素所在单元的内容已改变,当然在主调函数中可以利用这些已经改变的值。

我们来分析一下下面的案例

问题: 用选择法对10个整数进行排序

选择排序的思路是:
每一轮从待排序列中选取一个值最小的元素,将它和当前序列的第一个元素互换。
假定有n个元素存放在a[0]a[n-1]中,第一轮从这n个元素中选取值最小的元素,将它和a[0]互换,此时a[0]中存放了n个数中最小的数;第二轮从a[1]a[n-1]中这n-1元素中选取值最小的元素,将它和a[1]互换,此时a[1]存放的就是n个数中次小的数;依次下去,共进行n-1轮比较,a[0]~a[n-1]就按由小到大顺序存放了。

#include <iostream>
using namespace std;

void sort(int *x,int n){ //通过x指针来访问数组元素,n表示数组元素个数 
	for(int i=0;i<n-1;i++){	//n个数只需要进行n-1次排序 
		int k = i;		//用来保存当前序列首元素下标 
		for(int j = i+1;j<n;j++){
			if(*(x+j)>*(x+k)){
				k = j;		//若存在比当前序列首元素大的数字就记录其下标 
			}
			if(k!=i){	//若 k!=i 便说明在后面序列中存在比首元素大的元素,则进行交换  
				int t = *(x+i);
				*(x+i) = *(x+k);
				*(x+k) = t;
			}
			
		}
	}
}

int main(void)
{
	int a[10];
	for(int i=0;i<10;i++){
		cin>>a[i];
	}
	sort(a,10);
	for(int i=0;i<10;i++){
		cout<<a[i]<<" ";
	}
	cout<<endl;
		
	return 0;
} 

指针型函数

函数可以通过 return语句返回一个单值的整形数,实型数或字符值,也可以返回含有多值的指针型数据,即指向多值的一个指针(即地址),这种返回指针值的函数称为指针型函数。
指针型函数的一般定义形式为:

数据类型 *函数名(形参表)
{
	函数体
}

其中函数名之前加 *表明这是一个指针型函数,即返回值是一个指针,数据类型表明返回的指针值所指向的数据类型。
通过下面求数组元素最大值的案例来进行分析学习!

#include <iostream>
using namespace std;

int *max(int *x,int n){
	int *m;
	m = x;
	for(int j=0;j<n;j++){
		if(*(x+j)>*m){
			m = x+j;
		}
	}
	return m;
}

int main(void)
{
	int *p,a[10];
	for(int i=0;i<10;i++){
		cin>>*(a+i);
	}
	p = max(a,10);
	cout<<"max = "<<*p<<endl;
	
	return 0;
}

运行结果如下:
20230119_2
本例定义了一个指针型函数max,在它的形参中定义整形的指针变量x,用于接收实参数组a的起始地址,整型变量n用于接收实参数组a的元素个数。在被调函数max执行时,通过循环求得实参a数组的最大值的地址存放于指针变量m中。最后将m中的值即整形的地址带回到主函数中赋给指针变量p,使它指向a数组中值最大的元素并将其输出。

指向函数的指针

C++语言程序中,每个函数在编译连接后总是占用一段连续的内存区,而函数名就是该函数所占内存区的入口地址(起始地址),该入口地址就是函数的指针。在程序中可以定义一个指针变量用于指向函数,然后通过该指针变量来调用它所指向的函数。这种方法能够大大提高程序的通用性和可适应性,因为一个指向函数的指针变量可以指向程序中任何一个函数。
函数指针声明的一般形式为:

数据类型 (*指针变量名)(形参表)

其中,“数据类型"表示被指函数的返回值的类型。”(*指针变量名)"表示 *后面的变量是定义的指针变量。第二个小括号表示指针变量所指的是一个函数,形参表表明被指函数的形参类型和个数。例如:

int (*pf)(int,int);

该程序表示pf是一个指向函数入口的指针变量,被指函数的返回值(函数值)是整型,调用时需要给两个整型的实参值。应特别注意(*pf)两边的小括号不能少,如果写成

int *pf(int,int);

则不是变量说明而是函数说明。 *pf两边没有小括号,说明pf是一个指针型函数,其返回值是一个指向整型亮的指针。
函数指针变量在声明后,需要先为其赋值,使该指针变量指向一个已经存在的函数入口,然后才能对该指针函数变量进行使用。
赋值方式为:

指针变量名 = 函数名;

赋值号右边的函数名必须是一个已经声明过的函数,且该函数需和赋值号左边的函数指针变量具有相同类型返回值和形参表。在赋值之后,就可以通过该函数指针变量调用它所指向的函数了。
调用方式为:

(*指针函数名)(实参表)

使用函数指针变量还应注意:
1. 函数指针变量不能进行算术运算,这与数组指针变量不同。
2. 数组指针变量相减一个整数可以使指针移动只想后面或前面的数组元素,而函数指针的移动是毫无意义的
下面通过案例来说明用指针形式实现对函数调用的方法。

#include <iostream>
using namespace std;

int add(int a,int b){
	return a+b;
}

int sub(int a,int b){
	return a-b;
}

int result(int (*p)(int,int),int a,int b){
	int val;
	val = (*p)(a,b);
	return val;
}
int main(void)
{
	int i,j,value;
	cout<<"input two integer i,j:";
	cin>>i>>j;
	value = result(add,i,j);
	cout<<i<<"+"<<j<<"="<<value<<endl;
	value = result(sub,i,j);
	cout<<i<<"-"<<j<<"="<<value<<endl;
	
	return 0;
} 

运行结果如下:
20230119_3

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/173541.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Linux下dmi信息分析工具dmidecode原理

dmidecode命令主要是通过DMI获取主机的硬件信息&#xff0c;其输出的信息包括BIOS、系统、主板、处理器、内存、缓存等等。它是通过SMBIOS&#xff08;System Management BIOS)来获取信息的。SMBIOS是主板或系统制造者以标准格式显示产品管理信息所需遵循的统一规范。 什么是DM…

QA特辑 | 以万变钳制黑灰产之变的验证码产品设计逻辑的答案,都在这里

1月12 日下午&#xff0c;就验证码的攻防对抗问题&#xff0c;顶象反欺诈专家大卫从验证码的破解手段讲起&#xff0c;从防御角度深度剖析如何应对黑灰产的攻击以及验证码在产品能力设计层面应该考虑哪些问题。 直播也吸引了众多关注验证码的观众前来围观&#xff0c;针对验证…

vue3性能优化

文章目录1. Lighthouse1.1 性能参数2. rollup-plugin-visualizer&#xff08;打包代码块分析&#xff09;3. vite配置优化4. PWA离线缓存技术5. 其他优化1. Lighthouse 谷歌浏览器自带的 DevTools 也可以全局安装Lighthouse # 安装 yarn global add lighthouse# 使用 lighth…

Android app集成微信支付

Android app集成微信支付 鉴于微信支付的文档入口不太容易找到、以及文档中有些逻辑不通或者容易产生歧义或者缺失一些信息的情况&#xff0c;记录下此次接入的流程和需要关注的一些点。 使用的是app支付-> APP支付产品介绍 首先阅读介绍等&#xff0c;了解一些基础的概念…

c++数据结构-图(详解附算法代码,一看就懂)

图&#xff08;Graph&#xff09;是一种复杂的非线性结构&#xff0c;它可以描述数据间的关系&#xff0c;被广泛使用。图 G 由两个集合 V 和 E 组成&#xff0c;记为 。V是顶点的有穷非空集合&#xff0c;E是边的集合。通常&#xff0c;也将 G 的顶点集和边集表示为 V(G) 和 E…

尚医通-登录注册搭建-JWT(二十八)

目录&#xff1a; &#xff08;1&#xff09;前台用户系统-登录注册-需求分析 &#xff08;2&#xff09;前台用户系统-登录注册-搭建环境 &#xff08;3&#xff09;前台用户系统-手机登录-基本实现 &#xff08;4&#xff09;前台用户系统-手机登录-整合JWT &#xff08;…

【JUC并发编程】使用多线程可能带来什么问题

【JUC并发编程】使用多线程可能带来什么问题? 文章目录【JUC并发编程】使用多线程可能带来什么问题?什么是多线程并发为什么会出现线程带来的安全性问题可见性问题原子性问题有序性问题活跃性问题性能问题引起线程切换的几种方式什么是多线程 多线程意味着你能够在同一个应用…

Linux的ZONE_DMA,ZONE_NORMAL,ZONE_HIGHMEM及分配页释放页函数的简单介绍

Linux的ZONE_DMA&#xff0c;ZONE_NORMAL,ZONE_HIGHMEM及分配页释放页函数的简单介绍简单介绍一下页&#xff1a;Linux 区&#xff1a;分配页系统调用释放页系统调用简单介绍一下页&#xff1a; 内核把物理页作为内存管理的基本单位。 尽管处理器的最小可寻址单位通常为字(甚至…

ZooKeeper-分布式锁实现

4.11)Zookeeper分布式锁-概念 •在我们进行单机应用开发&#xff0c;涉及并发同步的时候&#xff0c;我们往往采用synchronized或者Lock的方式来解决多线程间的代码同步问题&#xff0c;这时多线程的运行都是在同一个JVM之下&#xff0c;没有任何问题。 •但当我们的应用是分…

【JavaScript】实现简易购物车

&#x1f4bb;【JavaScript】实现简易购物车 &#x1f3e0;专栏&#xff1a;有趣实用案例 &#x1f440;个人主页&#xff1a;繁星学编程&#x1f341; &#x1f9d1;个人简介&#xff1a;一个不断提高自我的平凡人&#x1f680; &#x1f50a;分享方向&#xff1a;目前主攻前端…

客快物流大数据项目(一百零四):为什么选择Elastic Search作为存储服务

文章目录 为什么选择Elastic Search作为存储服务 一、​​​​​​​​​​​​​​ElasticSearch简介

【GD32F427开发板试用】懒人新手试用

本篇文章来自极术社区与兆易创新组织的GD32F427开发板评测活动&#xff0c;更多开发板试用活动请关注极术社区网站。作者&#xff1a;东东_dxGGN2 我收到的开发板是GD32F427R-START&#xff0c;MCU是GD32F427RKT6&#xff0c;如下图&#xff08;座机拍的见谅&#xff09; 测试流…

【C++】从0到1入门C++编程学习笔记 - 核心编程篇:内存分区模型

文章目录一、程序运行前二、程序运行后三、new 操作符C程序在执行时&#xff0c;将内存大方向划分为4个区域 代码区&#xff1a;存放函数体的二进制代码&#xff0c;由操作系统进行管理的全局区&#xff1a;存放全局变量和静态变量以及常量栈区&#xff1a;由编译器自动分配释…

2022年回顾 | 被磨砺,被厚待

岁末年首&#xff0c; 最宜盘点过往的时光。 回顾2022团结一心&#xff0c;攻坚克难&#xff0c; 祝福2023大展宏图&#xff0c;鹏程万里。 2022我们遇到了"卷土重来"、 “挥之不去”&#xff0c; 也等到了"再也不见"和 “永远下线”。 2022是一个&…

HTML中的table标签与a标签

这里写自定义目录标题一、table标签1、什么是table标签2、table标签中长见到的标签3、例子代码及其结果二、a标签1、什么是a标签2、a标签中常见的属性3、例子代码及其结果一、table标签 1、什么是table标签 table标签表示整体的一个表格 2、table标签中长见到的标签 <tr…

基于Spring Boot和Spring Cloud实现微服务架构

首先&#xff0c;最想说的是&#xff0c;当你要学习一套最新的技术时&#xff0c;官网的英文文档是学习的最佳渠道。因为网上流传的多数资料是官网翻译而来&#xff0c;很多描述的重点也都偏向于作者自身碰到的问题&#xff0c;这样就很容易让你理解和操作出现偏差&#xff0c;…

采用特殊硬件指令对密码学算法加速

1. 引言 Armando Faz-Hermandez等人2018年论文《SoK: A Performance Evaluation of Cryptographic Instruction Sets on Modern Architectures》&#xff0c;开源代码见&#xff1a; https://github.com/armfazh/flo-shani-aesni&#xff08;C语言&#xff09; slide见&…

Java高手速成 | 多态性实战

多态性&#xff08;polymorphism&#xff09;是OOP最强大、最有用的特性。截至目前&#xff0c;多态性用到了所讲的所有其他OOP概念和特性。在通向精通Java语言编程的征程上&#xff0c;多态性是最高级别概念站点。 一个对象具有跟另一不同类的对象一样的行为&#xff0c;或者具…

QT5.14.2使用回顾

前面已有博客介绍了QT的安装和配置VS2019配置Qt5.14.2以及在线配置Qt5.15.2&#xff0c;这里再接着该版本说明下QT的使用&#xff0c;主要是汇总下之前博客中的内容&#xff1a;Ubuntu下的基本知识点&#xff08;二&#xff09;QT4.8.6工程到QT5.12.1的迁移注意前面安装时候&am…

小程序开发超好用的UI组件——Vant Weapp

Vant Weapp 是有赞前端团队开源的一套小程序 UI 组件库&#xff0c;助力开发者快速搭建小程序应用。它所使用的是 MIT 开源许可协议&#xff0c;对商业使用比较友好,官网地址&#xff1a;https://vant-contrib.gitee.io/vant-weapp/#/home 安装 Vant 组件库 在小程序项目中&a…