C/C++语言指针详解(一)

在这里插入图片描述

更多内容请关注冯老师的公众号

——循序渐进C++

可以说指针是C语言的精髓,因为有了指针,C语言可以和汇编语言比效率。在教学过程中,指针这部分内容常常给学生带来困惑。下面我来说说指针在C及C++语言中的用法。

1.指针的定义

上一篇文章,变量的三个要素之一,变量的值包括变量的数据值和变量的地址值。这个地址值也可以由另外的一个变量来存储,这个变量就需要是指针变量。
指针的定义及相关术语
(1)指针用来存放某个变量的地址的值的变量,这个变量与一般变量不同,它所存放的值是某个变量在内存中的地址的值。
(2)一个指针变量存放了哪个变量的地址,就说这个指针指向了哪个变量。
(3)指针必须初始化或者赋值(指向了变量数据)后,才能进行间接访问操作
内存空间的访问方式:
 通过变量名访问
 通过地址访问(通过指针间接访问它所指向的变量的值)

取地址符:&
例:int x;
则&x 表示变量x在内存中的起始地址,x是整型数据,占四个字节的内存单元
那么&x就可以存放在一个指针变量里。这就需要定义一个指针变量。

2. 指针定义格式

语法格式
存储类型 数据类型 指针名=初始地址;
例: int pi=&x;
说明:
(1)这个语句就定义了pi这个指针变量,pi是指针名,int是数据类型,是指这个指针变量要存放的是int类型变量的地址,其他类型变量的地址不可以给指针变量pi赋初值或赋值。
(2)语句虽然没有给指针指定存储类型,但是有默认的存储类型auto类型,即存储类型为自动类型,也就是函数级的自动类型变量,存放在栈空间,由编译系统管理pi变量的所占用的内存空间。
(3)这条声明语句中的“
”号非常重要,只有有了“
”号,才能说明pi是指针变量,才能存放另外一个整型变量的地址;
(4)声明了指针变量pi,并且给这个变量赋了初值为x变量的地址,我们就可以说pi指向了x;
(5)定义指针变量的时候可以赋初值,也可以不赋初值,如果赋初值需要注意的是该变量必须在指针初始化之前已声明过,且变量类型应与指针类型一致。也可以用一个已赋初值的指针去初始化另一 个指针变量。
(6)既然pi是一个指针变量,那么pi也会分配内存单元,也占用内存空间,因此也有地址,&pi就是指针变量的地址,不管指向什么类型的变量的指针,所占用的空间大小是一样的,都是4个字节的内存单元。
(7)如果指针没有付初值,那么在通过使用指针间接它所指向的变量前,一定要赋值,这样也很好理解,指针没有指向变量,怎么能间接访问这个变量的数据值呢?因此说使用指向前,指针不能为空
我们来看这个程序段

#include <iostream>
using namespace std;
int main()
{
	int x = 100;//声明x变量,并赋初值100
	int *pi = &x;//声明pi指针变量,并赋值为x的地址
	cout << x << endl;//输出x变量的数据值
	cout << &x << endl;//输出x变量的地址值
	cout << *pi << endl;//通过指针间接访问x变量的值
	cout << pi << endl;//pi变量的值,就是x的地址
	cout << &pi << endl;//pi变量的地址值
	getchar();
	return 1;
}

执行结果如下图1所示
图1  程序代码执行结果
图1 程序代码执行结果
分析上面语句可以看出,pi指针保存的是x变量的地址,第2条输出语句和第4条输出语句的结果是一样的,都是x变量的地址00F2FBFC,是8个16进制字符,也就是说是一个32位的长整型数据,表示指向的变量在内存中的编号。
对于第3条输出语句cout << pi << endl;的输出结果为x变量的值100,在开始学习这里内容学生会有疑惑,都是因为那个声明语句int pi = &x;带来的困惑。 在这个声明语句中,看似pi赋值为&x,怎么到了输出pi时,pi就成了x变量的值了呢?
因为那个是声明语句,声明语句中的“
”号,只是表示后面的变量pi是指针类型,只有是指针类型,才可以赋值为另外一个变量的地址,离开了这个声明语句,那个“*”号,就成了通过指针访问变量的取内容运算符了。因此
cout << *pi << endl;
输出pi指针指向的变量的值。
图1结构中x变量的数据值、x变量的地址、pi变量的数据值、pi变量的地址值,在内存中的模型示意图,如图2所示
在这里插入图片描述
图2 pi指针指向x变量的物理内存模型示意图
如果画pi变量指向x变量的逻辑图,就很简单了,示意图如图3所示
在这里插入图片描述
图3 pi指针指向x变量的逻辑示意图
两个格子,分别表示pi变量和x变量,画一个箭头,箭尾画在pi指针变量的小格子里面,箭头指向x的小格子,不要指到x变量的小格子里。这样就表示pi指针存放的是x变量的地址。

3. 指针变量可以指向不同的数据类型

定义指针变量:
1)int *pi;
//pi是一个指向int型变量的指针;
2)float *pl;
//pl是一个指向float型变量的指针;
3) char *pc;
//pc是一个指向char型变量的指针;
4)char (*pa)[3];
//pa是一个指向一维数组的指针,这个一维数组是长度为3的字符数组;
5) char *pm[3];
//pm是一个指针数组,pm是数组名,这个数组的三个元素是字符变量的地址
6)int (pf)();
//pf是一个指向函数的指针,该函数的返回值为int型数值;
//也就是说指针不仅可以指向数据,也可以指向代码,函数就是代码段、
//函数名就是函数的地址
7)int * fun();
//函数的返回值是指针,是指向整型变量的指针
8)int **pp;
//pp是一个指向指针的指针,即二级指针 。
//要存储一个指针变量的地址,就需要一个二级指针
//如果pi是执行整型变量的指针
//pp变量就可以存储pi变量的地址
//一个“
”号,*pp表示间接访问pp指向变量的值,就取得了pi变量的值,
//pi变量是x变量的地址,因此**pp才可以访问到x变量的值。
9)
struct DATE{
int year,month,day;//三个结构体成员
};//定义结构体变量DATE
DATE *pd;//定义指向结构体变量的指针;
10)

struct NODE
   {	
     int   data;
     struct NODE *next; //结构体成员是指向自身结构的结构体变量的指针
};

这种结构体类型也叫自引用类型
11)

 class PERSON  //定义PERSON类
{
 private:
 char *name; //姓名  类的成员是指向字符的指针
      Int  age; //年龄
     char   gender;//性别
     public:
     PERSON(char *n,int nl,char xb) //类的构造函数
{
  strcpy(name,n);
   age=nl;
gender=xb;
}
     }; 
  PERSON  *pa;//pa是指向PERSON类的指针

4.指针的运算

指针有三种运算:赋值运算、算术运算和关系运算。

(1)赋值运算

说明:
指针名=地址
“地址”中存放的数据类型与指针类型必须相符。
b. 向指针变量赋的值必须是地址常量或变量,不能是普通整数。但可以赋值为整数0,表示空指针。
c. 指针的类型是它所指向变量的类型,而不是指针本身数据值的类型,任何一个指针本身的数据值都是unsigned long int型。
d. 允许声明指向 void 类型的指针。该指针可以被赋予任何类型对象的地址。
void vobject;
//错,不能声明void类型的变量
void *pv;
//可以声明void类型的指针
int *pint;
int i;
void main()
//void类型的函数没有返回值
{
pv = &i;
//void类型指针指向整型变量
pint = (int *)pv;
// void指针赋值给int指针需要类型强制转换:
}

(2)算术运算

指针与整数的加减运算
指针 p 加上或减去 n ,其意义是指针当前指向位置的前方或后方第 n 个数据的地址。
这种运算的结果值取决于指针指向的数据类型。
指针加一,减一运算
指向下一个或前一个数据。
例如:y=*px++ 相当于 y=*px;px++
(*和++优先级相同,自右向左运算,但是如果++运算符是后缀,尽管++运算符优先级高,由于它的风格更高,因此它会让指针先进行取内容运算,再做加1运算)
两个指针相减
两个指针相差数据的个数。
说明:两个指针不能做加法运算,因为两个指针相加没有意义 。

#include <iostream>
using namespace std;
int main()
{
 	int i = 5;//声明变量i
 	int *p = &i;//声明p指针指向变量i
 	cout << "p为" << p << endl; //i的地址
 	cout << "*p为" << (*p)++ << endl; //5
 	//通过指针p访问i变量,语句执行结束后,把i变量的值加1
 	cout << p << endl;//依然是i变量的地址
 	cout << *p << endl;//i变量的值是6
 	getchar();
 	return 1;
}	

输出结果如图4所示。
上面代码第4行,如果把括号去掉,结果会有很大的不同
如果把cout << “*p为” << (*p)++ << endl;
语句改成
cout << “*p为” << *p++ << endl; //5
输出的结果依然是5,但是这条语句执行结束后,不是指针间接访问的变量加1,而是指针变量加1,一个指针加上常量1,那么指针就指向整数i变量后面的数据了,因此后面的输出语句cout<<p<<endl;//这个是变量i后面整数的地址
cout<<*p<<endl;//间接地访问了i变量后面地址单元里的内容
输出结果如图5所示。
在这里插入图片描述
图4
在这里插入图片描述
图5
指针加减一个常数后,指针值的大小和指针指向数据类型有关,
如图6所示
在这里插入图片描述
图6 指针加减一个常数,移动字节数与指向变量的数据类型有关
总结:
指针加减整数的操作表示空间位置上的移动;但是移动的字节数与指针所指数据类型相关。
对float指针加6实际增加了24个字节
对 int指针加5实际增加了20个字节
对char指针减7实际减少了7个字节
对double指针减2实际减少了16个字节

(3)关系运算

两个指针可以做 <,<=,>,>=,==,!=这样的关系运算。
图下程序段,
int a[10]={10,20,30,40,50,60,70,80,90,100};
//定义一个长度为10的数组
int *p=&a[0];//p 指针指向第一个数组元素
int *q=&a[9]; //q指针指向第10个数组元素
while(p<=q)//循环条件,两个指针做关系运算
{
Cout<<*p<<”\t”; //通过指针间接访问数组中的每个数据元素
p++;
}

5.指针与数组

数组名是一个指针常量,一个数组的数组名就是该数组首地址的值。指针常量不同于指针变量,指针常量的值不能改变,但指针变量的值是可以改变的。
说明(或声明)与赋值
例: int a[10], pa;
pa=&a[0]; 或 pa=a; 两个赋值语句都是让pa指针指向数组的第一个元素
通过指针使用用数组元素,经过上述声明及赋值后:
pa就是a[0],(pa+1)就是a[1],… ,
(pa+i)就是a[i].
a[i], *(pa+i), *(a+i), pa[i]都是等效的。都是表示数组下标为i的数组元素,
但是不能写 a++,因为a是数组首地址是常量。

#include <iostream>
using namespace std;
int  main()
{
static int a[5={5,4,3,2,1};
int i,j;
i=a[0+a[4;
j=*(a+2)+*(a+4);// 等价于 j=a[2]+a[4] 结果为4
cout<<i<<endl<<j;
getchar();
return 1;
} 

6.字符指针

字符指针是指向字符串的指针。字符指针便于对字符串进行操作。
如:char *p=“abcd”,*q;
q=“mnpq”;
字符指针比较特殊,比如p是字符串“abcd”的首地址,q存放的是“mnpq”字符串的首地址
cout<<*p<<endl;//输出字符a
cout<<*q<<endl;//输出字符m

cout<<p<<endl;//不会输出地址值,而是输出从这个地址开始,到’\0’结束的字符串,因此这个语句的输出结果是“abcd”这个字符串,因为字符串就是以‘\0’结束的。
同样cout<<q<<endl;输出结果是“mnpq”这个字符串。

7.指针数组

数组的元素是指针的类型
定义格式:类型 *数组名[数组长度]
例: int *pa[2]; //pa就是指针数组,每个数组元素是整型变量的地址

#include <iostream>
using namespace std;
int main()
{
	int a[3] = { 1,2,3 };
	int* pa[3];
	pa[0] = &a[0];//pa[0]指向a[0] *pa[0]就是a[0]
	pa[1] = &a[1];//pa[1]指向a[1] *pa[1]就是a[1]
	*(pa + 2) = a + 2;//等价于pa[2]=&a[2] *pa[2]就是a[2]
	cout << *pa[0] << "," << **(pa + 1) << ',' << *pa[2] << endl;//1,2,3
	// *(pa+1)就是pa[1],pa[1]指向a[1];因此pa[1]还是一个地址
	//**(pa+1)等价于*pa[1]等价于a[1]
	getchar();
	return 1;
}

8. 指向数组的指针

指向一维数组的指针,可以认为是一个二级指针,因为数组可以表示为一级指针,因此它是指向一级指针的指针,故为二级指针。
定义格式:类型 (*指针名)[数组长度]
如:
int a[2][3]={1,2,3,4,5,6};
int (*pa)[3];
pa=a+1;
对于二维数组a,我们可以把它看成是一个一维数组,这个一维数组有两个数组元素,每个数组元素又可以看成是一个有三个数组元素的一维数组。
对于a来说,a[0]和a[1]是它的两个数组元素;
但是a[0]和a[1]分别是与三个数组元素的数组名;
a[0]这个数组中的三个数组元素是{a[0][0],a[0][1],a[0][2]}
a[1]这个数组中的三个数组元素是{a[1][0],a[1][1],a[1][2]}
这样看来,对于 a是一个二级指针就好理解了
a就是a[0],(a+1)就是a[1];
a是指针,通过指针取内容,取到了两个数组元素a[0]和a[1];
a[0]有是三个数组元素的数组名,a[0]就是a[0][0]同样是通过指针取内容
a[0]就是a),因为a是a[0],因此**a就是a[0][0],对于二维数组取到数组元素,就是把数组名看成了二级指针。
(a+1)+1呢?还是一个地址,a[1]是地址,地址加上一个常量1,是下一个元素的地址,a[1]是三个数组元素{a[1][0],a[1]以[1],a[1][2]}的首地址,a[1]+1 就是a[1][1]的地址,因此
(a+1)+1)就是数组元素a[1][1],以此类推,
(a+i)+j)是数组元素a[i][j]

using namespace std;
int main()
{
	int a[2][3] = { 1,2,3,4,5,6 };
	int(*pa)[3];
	pa = a + 1;//a是二级指针,a+1还是二级指针,pa指针指向了{a[1][0],a[1][1],a[1][2]}这三个元素的数组,pa+1或a+1实际移动的字节数是三个整型数据的空间,即12个字节
	cout << pa[1][0] << "," << pa[0][0] << "," << *(*pa + 2) << endl;
//pa[1][0]是数组a后面的数据,不是数组a中的数据,因此这个是随机值
//pa[0][0]就是a[1][0],值是4
//* *(*pa + 2)就是pa[0][2],是a[1][2]这数组元素,值是6
	getchar();
	return 1;
}

9.指针与函数

(1)形参是指针的函数
如果定义一个函数,形参定义的是指针,那么调用的时候,指针形参对应的实参一定是地址,我们把这种调用方式成为传地址调用。
先定义一个函数, 用指针变量作参数, 实现两个数的交换,然后在主函数中调用它。

#include <iostream.h>
 void swap2(int *x,int *y)
 
{  int temp;
  temp=*x;
  *x=*y;
  *y=temp;
  cout<<″x=<<*x<<,<<″y=<<*y<<endl;
}
void main()
{
  int a(5),b(9);
  swap2(&a,&b);
  cout<<″a=<<a<<,<<″b=<<b<<endl;
} 

(2)指向函数的指针
 定义格式如下:
类型标识符 (*指针变量名)( );
例如: int (*pf)( );
说明:
a.定义指向函数的指针变量, 可以指向一类函数。
b.定义指向函数的指针变量时, 如果将要赋值给它的函数是有形参的,那么定义指向函数的指针,后面的括号也要有形参,并且和赋值函数形参的类型一致,如果赋值函数没有形参,那么括号也不能省略。
如果这样定义int *pf( );这是函数声明语句,是指返回值是指向整型数据的指针值,而不是指向函数的指针变量。 
c. 对指向函数的指针变量pf, pf+i、 pf++、 pf–等运算无意义。
 让指针变量指向函数
定义了指向函数的指针变量,就可以在指针变量与特定函数之间建立关联,让指针变量指向特定函数。(指向不仅能指向数据,还能指向代码)
赋值格式
指针变量=函数名;
说明:a. 指针变量只能指向定义时所指定的一类函数。
b. 一个指针变量可以先后指向多个不同的函数
 利用指针实现函数调用
指针变量一旦指向某函数,利用指针所指向的变量可以实现函数调用。
一般形式: (*指针变量)(实参表);
指针名,就是通过指针间接访问它所指向的数据,现在指针指向了函数,那么指针名,就是调用函数体程序段。

/ 通过指针调用函数,求两个数的最大值*/

#include <iostream>
using namespace std;
int  max(int x,int y)/*求两个数的最大值*/
{
	int  z;
	z = (x >= y)?x: y;
	return(z);
}
int main()
{
	int (*pf)(int  x, int  y);
    /*定义指向函数的指针变量pf*/
	int  a,b;
	int  m;
	cin >> a >> b;
	pf = max; 
	/*建立关联,把函数名赋值给指向函数的指针变量*/
	m = (*pf)(a,b); /*利用指针实现函数调用*/
	cout << "a = " << a << ",b = " << b << ",max = " <<m << endl;
	getchar();
	return 1;
} 

(3)函数的返回值是指针
如库函数strcpy(char *to, char *from),此函数的功能是拷贝串from到串to,并返回一个指向串to起始地址的指针。在头文件string.h中出现的strcpy的原型就是char * strcpy(char * char *);
下面自已设计一个返回指针的函数makenode

typedef struct node
{
	char item;
	struct node * next; //结构体成员是指针
}node;
node* makenode(char item)  //函数的返回值是指针
{
	node* p;
	if ((p = new node) != NULL)
		p->item = item;
	return(p);
}
int main()
{
	node *s;
	s = makenode('$');
	cout << s->item << endl;//输出$
	getchar();
	return 1;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值