C++中指针的使用最详细解释(通俗易懂)

 2024-02-09 01:03:37  阅读 0

1. 指针的定义和使用

指针是高级编程语言中非常重要的概念,在高级语言编程中发挥着非常重要的作用。 它们可以让不同区域的代码轻松共享内存数据。 指针使得构造一些复杂的链接数据结构成为可能。 有些操作必须使用指针,例如申请堆内存,C++或C语言中函数调用中的值传递是按值传递的。 如果在函数中修改传递的对象必须通过this对象指针来完成。 指针是内存地址,指针变量是用来存储内存地址的变量。 不同类型的指针变量占用相同的存储单元长度,而存储数据的变量则因数据类型不同占用不同的存储空间长度。 。 指针不仅可以用来操作数据本身,还可以操作数据变量的地址。 很多新手在刚开始学习编程的时候都会对指针感到困惑,那么现在就让我来给大家详细讲解一下指针的使用吧。

1.指针的引入(函数返回中语句的限制)

功能缺点:

一个函数只能返回一个值。 即使我们在函数中写了太多的语句,只要执行任意一条语句,整个函数调用就结束了。

数组可以帮助我们返回多个值,但数组是相同数据类型的组合。 数组不能用于不同的数据类型。

使用指针可以有效解决这个问题。 使用指针,我们可以返回任意数量的值,并且可以返回任何我们想要返回的值类型。 在编程过程中,存储或检索数据都需要与存储单元打交道。 计算机通过地址编码来表示存储单元。 指针类型用于处理计算机地址数据。 计算机将内存划分为若干个存储空间大小的单元。 每个单元的大小为一个字节。 即计算机将内存一一转换为字节,然后为每个字创建一个字节。 一个节被分配一个唯一的代码,它是该字节的地址。 指针就是用来表示这些地址的,也就是说指针数据并不是字符数据,而是我们内存中的地址编码存储的。 指针可以提高程序的效率,更重要的是,它们使一个函数能够访问另一个函数的局部变量。 指针是两个函数之间进行数据交换不可缺少的工具。

地址和指针的概念:

程序中的数据(变量、数组等)对象始终存储在内存中。 这些对象在其生命周期内,占据一定的内存空间并具有一定的存储位置。 事实上,每个内存单元都有一个地址,即 Byte 是连续编码的。 编译器将程序中的对象名转换为机器指令识别的地址,并通过该地址存储对象值。

整数我; F;

计算机为int类型数据分配4个字节,为type分配8个字节。通过对象名访问对象的方法变成直接对象访问,如:i=100; f=3.14; 通过对象地址访问对象的方法就变成了指针间接访问。

如图所示,有一个名为4000的存储空间,它存储的不是数值,而是变量i的地址。 i 占用四个字节,每个字节都有一个地址。 该变量的地址实际上是第一个变量。 字节的地址。 这里i的地址指的是它的第一个字节的地址。 假设第一个地址是4000,这里有ap。 不管是什么,它都会占用存储空间。 里面放的是i的地址。 现在我们想要访问i,但是我们不直接访问,而且i的名字也没有出现。 相反,我们通过查找p得到i放在p中的地址,然后我们就可以间接访问i。 也就是说,我们不是直接访问这个地址,而是通过数量P来访问i的值,这种访问方式称为指针间接访问。 即通过对象的地址来存储对象的方法称为指针间接访问。

现在让我们通过一个例子来详细了解什么是指针。

当快递员给老客户投递包裹时,快递员可以直接前往客户办公室,将包裹交给她。 这是直接访问。 但如果快递员现在收到一个他不认识的人发来的快递,而且他是新客户,那么快递员就会跑到他的办公室。 办公室里人很多,他分不清谁是谁,所以快递员发现这里的保安问“张三是谁?” 保安会向他指出张三就是这个人。 然后快递员就可以将快件递送给客户了。 这时,过来指点张三的人(保安)充当了指点员的角色。 快递员无法直接找到张三,只能通过保安指出张三是谁。 这称为间接访问。

2. 形式方法的定义和指针的含义

C++调用专门用于存储对象地址的指针变量。 以下是指针变量的定义形式:

指针类型* 指针变量名;
例如:
int* p, i;	//定义指针变量p,i为整形变量
p = &i;		//指针变量p指向i
把 i 的地址放到p

数字100放在i中,它的地址是4000。i的地址放在p中。 那么p指向i。 它们之间的关系是p指向i。

假定指针变量p 的值是4000,三种写法:
char* p;
int* p;
double* p;
指针的类型其实就是它所指向的对象的类型

指针类型:

指针的类型表明了它所指向的对象的类型。 将p定义为char类型,即使用char类型指针P间接访问它所指向的对象时,我们间接引用的是一个字节空间。 假设p所在的位置是4000,系统会默认p指向的字节4000的内容。 如果p定义为整数,那么意味着当我们用指针间接引用指向一个对象时,系统会认为你的指针占用4个字节的对象是4000、4001、4002、4003。这四个字节一起形成这个对象。 如果定义为类型,系统会认为它指向的对象是8个字节,即4000~~4007,这8个字节就认为是P指向的对象,这就是指针的意义类型。 指针的类型应该与其所指向的对象的类型一致,即整型指针应该指向整型变量,而实型指针应该指向实型变量。

3.通过指针间接访问

可以通过间接引用操作*来访问指针所指向的对象或内存单元。 即在指针前面加*表示该指针所指的内容。

int a, * p = &a;
a = 100;//直接访问a(对象直接访问)
*p = 100;//*p就是a,间接访问a(指针间接访问)
*p = *p + 1;//等价于a=a+1

int a, b, * p1 = &a, * p2;
&*p1 的含义:
&和*都是自右向左运算符,因此先看p1是指针,*p1即为p1指向的对象,
因此*p1等价于a,因此a 的前面加一个&,表示的就是 a 的地址
因此:&*p1 ,p1 ,&a 三者等价
*&a 的含义:a的地址前加*表示的就是a本身,指针就是用来放地址的,地址前面加*表示的就是这个对象
因此: *&a ,a ,*p 三者等价

int main()
{
	int i = 100, j = 200;
	int* p1, * p2;
	p1 = &i, p2 = &j;//p1指向i,p2指向j
	*p1 = *p1 + 1;//等价于i=i+1
	p1 = p2;//将p2的值赋给p1,则p1指向j
	*p1 = *p1 + 1;//等价于j=j+1
	return 0;
}

4.指针的初始化,可以在定义指针的时候对其进行初始化

指针类型* 指针变量名 = 地址初值,......
int a;
int* p = &a;//p的初值为变量a 的地址
int b, * p1 = &b;//p1初始化是变量b已有地址值

由于指针数据的特殊性,他的初始化和赋值运算是有约束条件的,只能使用以下四种值:
(1)0值常量表达式:
int a, z = 0;
int p1 = null;  //指针允许0值常量表达式
p1 = 0;//指针允许0只常量表达式
下面三中形式是错误的:
int* p1 = a;//错误  地址初值不能是变量
int p1 = z;//错误  整形变量不能作为指针,即使值为0
p1 = 4000;//错误,指针允许0值常量表达式
(2)相同指向类型的对象的地址。
int a, * p1;
double f, * p3;
p1 = &a;
p3 = &f;
p1 = &f;//错误p1和f指向类型不同
(3)相同指向类型的另一个有效指针
int x, * px = &x;
int* py = px;//相同指向类型的另一个指针
(4)对象存储空间后面下一个有效地址,如数组下一个元素的地址
int a[10], * px = &a[2];
int* py = &a[++i];

5. 指针运算

指针运算只有在连续存储空间上操作时才有意义。

(1)指针加减整数运算
int x[10], n = 3, * p = &x[5];
p + 1		//指向内存空间中x[5]后面的第1个int 型存储单元
p + n		//--------------------------n(3)个
p - 1		//-------------------前面-----1个
p - n		//
(2)指针变量自增自减运算
int x[10], * p = &x[5];
p++		//p指向x[5]后面的第1个int型内存单元
++p		//-----------------1--------------
p--		//p指向x[5]前面的第1个int型内存单元
--p		//--------------------------------
(3)两个指针相减运算
设p1, p2是相同类型的两个指针,则p2 - p1的结果是两支针之间对象
的个数,如果p2指针地址大于p1则结果为正,否则为负
int x[5], * p1 = &x[0], * p2 = &x[4];
int n;
n = p2 - p1;//n 的值为4  即为他们之间间隔的元素的个数
运算方法:(p2储存的地址编码-p1储存的地址编码)/4  若是double类型则除以8  char类型除以1
(1)指针加减整数运算
int x[10], n = 3, * p = &x[5];
p + 1		//指向内存空间中x[5]后面的第1个int 型存储单元
p + n		//--------------------------n(3)个
p - 1		//-------------------前面-----1个
p - n		//
(4)指针的运算关系
设p1、p2是同一个指向类型的两个指针,则p1和p2可以进行关系运算,
用于比较这两个地址的位置关系即哪一个是靠前或者靠后的元素
int x[4], * p1 = &x[0], * p2 = &x[4];
p2 > p1;   //表达式为真

6. 指针的const限制

(1)指针变量可以指向只读对象,称为const对象指针。 定义形式为:

const 指向类型  *指针变量,...

即在指针变量前添加const限定符,意味着不允许通过指针改变const所指向的对象的值,也不能通过间接引用改变其所指向的对象的值。

const int a = 10, b = 20;
const int* p;
p = &a;//正确 p不是只读的,把a的地址赋给p,给p赋值是允许的
p = &b;//正确,p不是只读的
*p = 42;//把42赋给p所指向的对象。错误,*p是只读的

(2)将const对象的地址赋给非const对象的指针是错误的,例如:

const double pi = 3.14;
double* ptr = π//错误,ptr是非const所指向的变量
const double* cptr = π//正确,cptr是const指针变量

(3) 允许将非常量对象的地址赋给指向常量对象的指针。 不能使用指向 const 对象的指针来修改所指向的对象。 但是,如果指针指向非常量对象,则可以使用其他方法来修改所指向的对象。 目的

const double pi = 3.14;
const double* cptrf = π//正确
double f = 3.14;//f是double类型(非const类型)
cptr = &f;//正确,允许将f的地址赋给cptrf
f = 1.68;//正确,允许修改f的值
*cptrf = 10.3;//错误不能通过引用cptr修改f的值

(4)在实际编程过程中,常常使用指向const的指针作为函数的形参,以保证传递给函数的参数对象在函数中不能被修改。

void fun(const int* p)
{
	...
}
int main()
{
	int a;
	fun(&a);
}
指针作为函数的形参,在主函数中,我们定义整形变量a然后将a的地址传递给了子函数,对于子函数来说,
他的形参就是用const修饰过 的整型变量p指向主函数里a这个变量
这样的一系列操作就使得我们不能在子函数中通过p间接引用a 来改变a 的值,因为a 是用const修饰过的作就使得我们不能在子函数中通过p间接引用a 来改变a 的值,因为a 是用const修饰过的

7. 常量指针

指针变量可以是只读的,成为const指针及其定义形式:

指针类型* const 指针变量, ...;
注意观察将const放在变量名的前面,与上面的形式不同,
int a = 10, b = 20;
int* const pc = &a;//pc是const指针
pc = &b;//错误pc 是只读的
pc = pc;//错误pc是只读的
pc++;//错误pc是只读的
*pc = 100;//正确,a被修改

pc 是一个指向 int 对象的 const 指针

您不能将 pc 指定为指向其他对象。 任何向 const 指针赋值的尝试都会导致编译错误。

但是,可以通过pc间接引用来修改对象的值。

2、一维数组和指针 1、数组首地址

数组由若干个元素组成,每个元素都有一个对应的地址。 取地址运算符&即可得到每个元素的地址。 数组的地址是整个存储空间中第一个元素的地址,即a。 [0]

int a[10];
int* p = &a[0];//定义指向一维数组元素的指针,用a数组的地址来初始化p,称p指向a
p = &a[5];//指向a[5] 重新给p赋值,指针数组元素的地址跟取变量的地址是一样的效果,
c++中规定数组名既代表数组本身,又代表整个数组的地址,还是数组首元素的地址值即:与a第0个元素的地址& a[0]相同
例如:
下面两个语句是等价的:
p = a;
p = &a[0];
数组名是一个指针常量,因而他不能出现在左值和某些算数运算中
例如:
int a[10], b[10], c[10];
a = b;//错误,a是常量,不能出现在左值的位置
c = a + b;//错误,a,b是地址值,不允许加法运算
a++;//错误,a 是常量不能使用++运算

2. 指向一维数组的指针变量

定义指向一维数组元素的指针变量时,指针类型应与数组元素类型一致。

int a[10], * p1;
double f[10], * p2;
p1 = a;//正确
p2 = f;//正确
p1 = f;//错误,指向类型不同不能赋值

3.通过指针访问一维数组

由于数组的元素地址有规律地增加,根据指针运算的规则,可以使用指针及其操作来访问数组元素。

int* p, a[10] = { 1,2,3,4,5,6,7,8,9,0 };
p = a;//指向数组a,其实就是让p指向了a[0]
p++;//指向了下一个数组元素即a[1]

根据上图,我们设置:

a为一维数组,p为指针变量,p=a; 下面我们访问一个数组元素a[i];

(1)数组下标方法:a[i];

(2)指针下标法:p[i]; p已经包含了数组的地址,所以数组名和p是等价的,所以p[i]和a[i]含义相同

(3)地址引用法:*(a+i); a表示下标为0的元素的地址(a+i),即数组a倒数第i个元素的地址,即第i个元素。 那么*(a+i)就相当于地址加一个星号,表示这个地址对应的存储单元,或者对应的对象就是元素a[i]

(4)指针引用法:*(p+i); 将 a 替换为 p 与 (3) 含义相同

下面我们使用多种方法来遍历一维数组元素:

(1)下标法:优点是程序编写方法直观,可以直接知道正在访问的是哪个元素。

#include
using namespace std;
int main()
{
	int a[4];
	for (int i = 0; i < 4; i++)
		cin >> a[i];
	for (int i = 0; i < 4; i++)
		cout << a[i] << " ";
	return 0;
}

(2)通过地址间接访问数组元素

#include
using namespace std;
int main()
{
	int a[5],i;
	for (i = 0; i < 5; i++)
		cin >> *(a + i);
	for (i = 0; i < 5; i++)
		cout << *(a + i) << " ";
}

(3)通过指向数组的指针变量间接访问数组。 使用指针作为循环控制变量的好处是指针直接指向元素,不需要每次都重新计算地址,可以提高运行效率(当我们使用P的元素时,p本身有已经放入了这个元素的地址,所以计算机不再需要计算这个元素的地址,因为在取出一个元素时,需要先知道它的地址),使用自增和自减操作是非常有效的指针变量,可以使指针变量自动向前或向后指向数组的下一个或上一个元素

#include
using namespace std;
int main()
{
	int a[5], * p;
	for (p = a; p < a + 5; p++)
		cin >> *p;
	for (p = a; p < a + 5; p++)
		cout << *p << " ";
}

指针p的初始值为a,即它最初指向元素a[0]。 指针可以进行​​比较操作p。

辛>>*p; 是对 p 指向的数组元素的间接引用。

3.使用指针操作字符串

您可以使用字符指针来处理字符串。 该过程与通过指针访问数组元素相同。 使用指针可以简化字符串处理。

C++允许定义一个字符指针,它被初始化为指向一个字符常量。 一般形式为:

char* p = "C Language";
或者
char* p;
p="C Language"

在初始化期间,p 存储该字符串的字符地址,而不是字符串常量本身。 相当于一个char类型的指针,指向字符串“C”的首地址,即第一个元素C的地址。也就是说p指向字符串。

接下来我们通过字符串指针来访问字符串

char str[] = "C Language", * p = str;//p指向字符串的指针相当于p指向了str[0]
cout << p << endl;//输出:C Language  跟cout<

#include
using namespace std;
int main()
{
	char str[] = "C language", * p = str;
	cout << p << endl;
	return 0;
}

运行结果:

#include
using namespace std;
int main()
{
	char str[] = "C language";
	char* p = str;
	cout << p+2 << endl;
	return 0;
}

#include
using namespace std;
int main()
{
	char str[] = "C Language";
	char* p = str;
	cout << p << endl;
	cout << p+2 << endl;
	cout << &str[7] << endl;
	return 0;
}

通过字符指针遍历字符串

char str[] = "C Language", * p = str;
while (*p!='\0')cout << *p++;
判断p指向的元素是不是字符串结束的标志*p++ 的含义:先输出p指向的元素然后p++(后置增增,先做完其他事再自增)
假设从str[0]开始,p指向的是C满足(*p!='\0')因此执行循环,下一个循环p指向“空格”不是字符串结束的标志,继续
循环直到遇到字符串结束的标志后结束循环

例子:

#include
using namespace std;
int main()
{
	char str[100], * p = str;
	cin >> str;
	while (*p)p++;
	cout << "strlen=" << p - str << endl;
	return 0;
}

while(*p)p++;的含义:进入循环判断逻辑值p是否为真(非零为真,零为假),即判断p是否指向字符串末尾标记(字符串结束标记的ASLL代码为0)如果p指向的字符不是空字符,则括号内容为true,执行循环p++(p指向下一个字符),否则循环结束

p-str是指针减法运算,意思是看两个指针之间相隔了多少个元素。 这里的p已经是字符串结束标记,str代表str[0]。

注意:

指针可以指向数组,这就增加了另一种访问数组的方式。 单个指针无法替代数组来存储大量元素。

char s[100] = "Computer";
s是数组名不能赋值,自增自减运算
char* p = "Computer";
p是一个指针,他存放的是这个字符串的首地址
p是一个指针变量,他能指向这个字符串也能指向其他东西可以进行赋值和自增自减
1、存储内容不同
2、运算方式不同
3、赋值操作不同
s一旦赋初值之后就不能再用其他字符来赋值,然而p却能重新指向其他字符

int a = 10, * p;
int& b = a;
p = &a;
string s = "C++";
string* ps = &s;
cout << p << endl;  //输出指针p的值,a变量的首地址
cout << b << endl;  //输出b的值是10
cout << *p << endl;  //输出指针p指向的变量,即a的值10
cout << ps << endl;;  //输出指针ps的值,s变量的地址
cout << *ps << endl; //输出指针ps指向的变量的值,即“C++”

二维数组字符串:

char s[6][7] = { "C++","Java","C","PHP","CSharp","Basic" };

记忆形式

标签: 指针 指向 数组

如本站内容信息有侵犯到您的权益请联系我们删除,谢谢!!


Copyright © 2020 All Rights Reserved 京ICP5741267-1号 统计代码