当前位置:   article > 正文

指针——操作地址_指针取地址和取内容

指针取地址和取内容

C语言中被誉为功能强大、也被认为“底层操作”的一个数据结构就是指针

一、指针的概念、定义与内存分配

指针与普通变量一样都用于存储数据,不同之处在于普通变量存储的是实际的数据,而指针变量存储的是地址数据存储在哪块内存区域)

1、定义普通变量

int a=1;
定义了变量a,存储int型的数据,值为1。内存中有一块内存空间存放a的值,对a的存取操作就是直接到这个内存空间存取,内存空间的位置叫做地址。

取地址运算符:&
取内容运算符:*

试运行下面的代码:

#include<bits/stdc++.h>
using namespace std;
int main(){
	int a=1;
	cout<<a<<endl;
	cout<<&a<<endl;
	cout<<*(&a)<<endl;
	return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

在这里插入图片描述
0x74ff0c即为变量a的地址。windows系统内存地址一般为24位二进制,表现为6个十六进制数。

2、定义指针变量

int *p=NULL;
定义了一个指针变量p,p指向一个内存空间,里面存放的是一个内存地址。现赋值为NULL(其实就是0,表示特殊的空地址)

3、给指针变量p赋值

p=&a;
把a的地址给了(例如0x74ff04)给了p,即p的值为a的地址;* p的值为变量a的值,即 *p 等价于a。

阅读下列代码:

#include<bits/stdc++.h>
using namespace std;
double a,b,c;
double * p;
int main(){
	cin>>a>>b>>c;
	if(a>b)	p=&a;
	else p=&b;
	*p+=c;
	cout<<*p;
	return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

在这里插入图片描述
代码含义:取a、b中的较大值加上c并输出。
说明:
1、变量a b c的地址是固定不变的,程序在任何时候访问普通变量都是一开始定义变量时给定变量的地址。
2、* p的空间是不确定的,一开始p的值默认为NULL。直到“p=&a或b”时p才有了地址,* p也才有了内容。
3、综上可以看出,指针是动态的数据结构,int double等是静态的数据结构。指针的动态性还体现在可以动态地申请内存空间。例如:"int *p;"定义时p没有空间,但在程序需要时,可以通过"p=new(int)"语句向操作系统申请一个内存空间,并把地址赋给p。以后就可以进行“ * p=100”之类的操作了。

阅读下列两段代码:
在这里插入图片描述
在这里插入图片描述

小结:
指针变量与普通变量类似,只不过普通变量存储数值,指针变量存储地址,通过&p也可以取出指针的地址,从而进一步进行运算操作,也称为“指针的指针”。指针是C语言中为数不多的“底层操作”,通过指针我们可以对“地址”进行操作。

二、指针的引用与运算

对应关系:

p&a
*pa
*p=1a=1

1、指针变量的初始化

方法说明
1int *p=NULLNULL是特殊的地址0,叫零指针
2int a; int *p=&a;p初始化为a的地址
3int *p=new(int);申请一个空间给p,*p内容不确定

需要注意的是,对于定义的局部指针变量,其内容(地址)是随机的,直接对他操作可能会破坏程序或系统内存的值,引发不可预测的错误。所有编程中指针变量要保证先初始化或赋值,给予正确的地址再使用。

2、指针变量的±运算

指针变量中存放的地址,它有两个常用运算:加、减,这两个运算一般都是配合数组使用。

通过指针访问数组:

#include<bits/stdc++.h>
using namespace std;
int a[100],n;
int * p;
int main(){
	cin>>n;
	for(int i=1;i<=n;i++)
		cin>>a[i];
	int *p=&a[1];
	for(int i=1;i<=n;i++){
		cout<<*p<<' ';
		p++;
	}
	return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

在这里插入图片描述
说明:
1、p++是“广义的加1”,不是p的值(地址)加1,而是根据p的类型 int 增加 sizeof(int) ,即刚好跳过一个整数的空间,到达下一个内容的地址。p-- p+=2也同理。
2、 * p+3 和 * (p+3)是不同的,* p+3相当于a[1]+3, * (p+3)相当于a[4]

3、无类型指针

有时一个指针根据情况不同,指向的内容是不同类型的值,我们可以先不明确定义它的类型,只是定义一个无类型的指针,以后根据需要再用强制类型转换的方法明确它的类型。

使用样例:

#include<bits/stdc++.h>
using namespace std;
int a=10;
double b=3.5;
void * p;
int main(){
	p=&a;
	cout<<*(int*)p<<endl;
	p=&b;
	cout<<*(double*)p<<endl;
	return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

在这里插入图片描述
说明:无类型指针p输出时,必须明确p指向的空间存放的数据类型。因为不同类型不仅空间大小不同,存储的格式也不同。比如把 * (double * )p 改成 * (long long *)p,输出的结果将是:4615063718147915776

4、多重指针

指针变量既然是变量,那么指针变量也是有内存地址的,C语言中允许将指针的地址也存放在指针中–多重指针,也叫指向指针的指针。

两重指针样例代码

#include<bits/stdc++.h>
using namespace std;
int a=10;
int *p;
int **pp;
int main(){
	p=&a;
	pp=&p;
	cout<<a<<endl;
	cout<<*p<<endl;
	cout<<**pp<<endl;
	return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

在这里插入图片描述
说明:**pp通过两次取内容操作访问了变量a的值10。多重指针除了可以多次“间接”访问数据,OI上的主要应用是动态的多维数组,以后再讲,在此不作赘述。

三、指针与数组

1、数组名可以当作常量指针使用

#include<bits/stdc++.h>
using namespace std;
int a[]={1,2,3,4,5,6};
int *p=a+4;
int main(){
	cout<<*a<<endl;
	cout<<*(a+3)<<endl;
	cout<<*(++p)<<endl;
	return 0;
}
//运行结果:1 4 6
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

说明:
1、程序中直接拿a当指针用,a指向数组的开始元素。
2、a是常量指针,a=a+2是非法的;p是变量指针,p=p+2是合法的。因此指针也称为动态数据结构,而数组是静态数据结构
3、c语言的scanf读取时就使用了指针技术,读入变量时需加一个‘&’取变量的地址传给scanf。对于数组可以直接用数组名作指针。

样例:通过scanf使用数组名

#include<bits/stdc++.h>
using namespace std;
int a[5];
int main(){
	for(int i=0;i<5;i++)
		scanf("%d",a+i);
	for(int i=0;i<5;i++)
		printf("%d ",a[i]);
		//也可写成printf("%d ",*(a+i));
	return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

2、指针可以当作数组名

指针可以动态申请空间,如果一次申请多个变量空间,系统给的地址是连续的,就可以当成数组使用,这就是传说中的动态数组的一种。

例题:动态数组,计算前缀和数组。b是数组a的前缀和的数组定义:b[i]=a[0]+a[1]+a[2]+…+a[i],即b[i]是a的前i+1个元素的和。

#include<bits/stdc++.h>
using namespace std;
int n;
int *a;
int main(){
	cin>>n;
	a=new int [n];
	for(int i=0;i<n;i++)
		cin>>a[i];
	for(int i=1;i<n;i++)
		a[i]+=a[i-1];
	for(int i=0;i<n;i++)
		cout<<a[i]<<' ';
	return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

说明:
1、在此指针a直接当做数组名使用
2、a=new int [n]; 的作用:向操作系统申请了n个连续的int型的空间。动态数组的优点:在保证小数据没问题的前提下,尽量满足大数据的需求

例题:行列转换。矩阵可以认为是 n * m 的二维数组。现在有一个巨大但稀疏的矩阵,N,M范围是[1…100000],有K个位置有数据,K的范围是[1…100000]。
矩阵输人的方式是从上到下 (第1行到第N行 )、从左到右(第1列到第M列)扫描,记录有数据的坐标位置(x,y) 和值(v)。这是照行优先的方式保存数据的,现在要求按列优先的方式输出数据,即从左到右、从上到下扫描,输出有数据的坐标和数值。
输入格式:第1行,3个整数N,M,K,范围都是[1…100000];下面有K行,每行3个整数:a be,表示第a行第b列有数据。数据在int范围内,保证是行优先的次序。
输出格式:1行,K个整数,是按照列优先次序输出的数
输入样例:
459
1 2 12
导展麻 1 4 23
2 2 56
srtlb<
2 5 78
3 2 100
3456
41 73
4 334
4 5 55本如器造
输出样例:
7312 56 100 34 23 56 78 55
解释:
73
12
56
100
34
23
56
28
55
分析:由于 N*M 可能会很大,直接开二维数组空间太大,不可行。解决问题的方法有很多种,下面序使用了指针和动态数组,根据每一列的实际数据个数来申请该列的空间,使每列的“数组”长度不同。算法是O(M+N+K)的时间复杂度( 即程序的运算量 ),O(N+K)的空间复杂度(即程序保存数据的内存大小),其他方法很难有这样优秀的效率。
程序如下:
93

四、指针与字符串

五、函数指针和函数指针数组

六、指针的拓展

小结

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/程序安全守护者/article/detail/63209
推荐阅读
相关标签
  

闽ICP备14008679号