以下为个人学习笔记和习题整理
课程:计算机程序设计(C++)- 西安交通大学 @ 中国大学 MOOC
https://www.icourse163.org/course/XJTU-46006
# 课堂笔记
# 指针的概念
指针是一种数据类型,指针与内存单元的地址密切相关。
# 内存单元
内存:由内存单元构成。
# 内存单元的地址和内容
内存单元的地址:指内存单元的编号
内存单元的内容:存放在内存单元中的数据
地址 | 内存空间 |
---|---|
0X28FED8 | 每个格子表示一个内存单元,占 1 字节长度 |
0X28FED9 | |
0X28FEDA | |
0X28FEDB | |
0X28FEDC | |
0X28FEDD |
# 内存单元的直接与间接访问
如果我们有钥匙,则可直接打开会议室的门;如果我们没有钥匙,但知道钥匙存放的地点(如钥匙存放在 509 号办公室),那么我们会按照这个地址取出钥匙,同样可以打开会议室的门。这是一种直接访问和间接访问的思想。
在 C/C++ 语言中,每个变量都分配有确定的内存空间。
使用变量名可直接访问内存中的数据;
通过变量的地址也可间接访问内存中的数据。
# 地址与指针
定义一个变量,系统按变量类型为变量分配不同数目的内存单元,将其第一个内存单元的地址作为变量的地址。
如: int a;
a
变量的内存分配如下所示。
地址 | 内存空间 |
---|---|
0X28FED8 | |
0X28FED9 | |
0X28FEDA | |
0X28FEDB | |
0X28FEDC |
a
的地址为: 0X28FED8
使用变量名 a
可直接存取内存单元中的值。
如:
a=5; // 赋值操作 | |
a+=a; |
在 C/C++ 中,允许定义一种特殊变量,用于存放某变量的地址。
现假设变量 pta
中存放着整型变量 a
的地址。 pta=&a;
pta
与变量 a
之间的关联,形象地表示为: pta->a
读作: pta
指向 a
。
其中: ->
为指针示意符。
由此,我们说 pta
中存放的是指向变量 a
的指针。即 pta
是一个指针变量。
将存放 “地址” 的变量称为指针变量,这里的 “地址” 就是指针。
因此,变量的地址就是变量的指针。
# 指针类型的主要用途
- 参数传递
指针作参数可以实现参数按引用传递的功能。 - 动态分配
利用动态分配可构建动态数组,动态数组需要借助指针实现 - 数据结构
创建可伸缩的数据结构,如链表、棧与队列、树和图等。 - 多态处理
面向对象编程中 “运行多态性” 的处理是利用指针与引用实现的。
# 指针变量的使用
变量有地址,指针变量可以存放变量的地址。
当指针变量中存放某个变量的地址后,我们就说该指针变量指向这个变量。
# 定义指针变量
即给指针变量分配内存空间。
<数据类型> *<变量名>; |
*
是指针类型变量的标志符号。<变量名>
为指针变量名(构成同标识符)。<数据类型>
为指针变量所指向变量的数据类型。<数据类型> *
表示指针类型。
如:定义一个指向字符类型的指针变量 pch
。
char *pch; //pch 是一个字符型指针变量 |
注意:变量
pch
的数据类型为char *
,而不是char
。
# 初始化
- 在定义指针变量的同时为指针变量提供初值。
如:int a=5,*pta=&a;
其中a
的初值为 5,pta
的初值为整型变量a
的地址。
这时,pta 与 a 的关联如下:
graph TD | |
B[&a,指针变量pta]--> A[变量] |
即,指针变量 pta
指向变量 a
, pta->a
。
- 使用赋值语句为变量提供初始值
上述定义语句:int a=5,*pta=&a;
与下面语句组的功能是等效的。
int a,*pta; // 先定义变量 | |
a=5; // 使用赋值语句提供初值 | |
pta=&a; // 使用赋值语句提供初值 |
注意:
pta=&a;
不可写成:*pta=&a;
因为,*pta
并不表示指针变量pta
,而表示pta
所指向的变量a
。
指针变量与指针变量所指向的变量是两个完全不同的概念。
# 定义多个
double *p1,*p2; |
定义 2 个双精度型的指针变量 p1
和 p2
,它们只能指向 double
型变量。
变量 p1 和 p2 的类型为: double *
。
每个指针变量前必须有 *
字符。
# 单目运算符 &
*
&
取地址运算符
&<变量名> // 获取变量的内存单元地址 |
*
指针运算符
也称为间接访问运算符
// 表示该指针所指向的变量 | |
*<指针变量名> | |
// 或 | |
*<指针常量> |
如果指针变量 pta
中存放着变量 a
的指针,则 *pta
表示 pta
所指向的变量即变量 a
。这是一种间接访问的表示。
int a=5,*pta=&a; | |
*pta=a+8; | |
cout<<a<<","<<*pta<<endl; | |
// 在这里,*pta 是表示 pta 所指的对象,即变量 a。 | |
// *pta 等同于变量 a | |
// 输出结果:13,13 |
int a=5,*p=&a; | |
cout<<&a<<endl;//a 的地址 0x23fe4c | |
cout<<a<<endl;//a 的值 5 | |
cout<<&p<<endl;//p 的地址 0x23fe40 | |
cout<<p<<endl;//p 的值 0x23fe4c | |
cout<<*p<<endl;//p 所指变量的值 5 |
注意:
*p
与*(&a)
等价,即就是a
。p
是一个指针变量,而&a
是一个指针常量。- 指针变量的值一定是 “地址”;指针变量所指对象的值不一定是 “地址”。
# 使用注意点
- 不要访问没有被初始化的指针变量。
如:
int *p; | |
cin>>*p; |
由于 p
变量未初始化, p
中可能存在一个不确定的单元地址,这时的输入将会改变原存储单元的值,造成结果混乱。
- 指针变量可以有空值,即该指针变量不指向任何变量。
常用符号常量NULL
表示空指针值,其实NULL
代表的值是整数0
。编译系统约定0
号单元不存放有效数据。
# 函数与指针
一个函数在编译时被分配一个入口地址,这个入口地址就称为函数的指针。
在 C++ 中, 函数名代表函数的入口地址。
# 指针作函数的参数
实现地址传递
用途如下:
- 指针作函数参数,这时形参接受的是实参的地址。函数中通过对指针的间接访问实现参数的按 “引用传递” 功能。
- 设置多个指针参数可从函数中带回多个结果值。
- 对于传递一块连续的内存区域数据,传递首地址比传递数据值,不仅开销小而且效率高。
# 例子:地址传递
编写交换两个变量值的函数。
- 关键点描述:
// 实现交换的函数 | |
void swap(int *xp, int *yp) // 形参为指针变量 | |
{ | |
int t; | |
t = *xp; | |
*xp = *yp; // 交换时通过间接访问运算符 | |
*yp = t; | |
} | |
// 主函数 | |
int main() | |
{ | |
int x=2,y=3; | |
cout<<"调用前:x="<<x<<",y="<<y<<endl; | |
swap(&x,&y); // 实参为变量的地址。 | |
cout<<"调用后:x="<<x<<",y="<<y<<endl; | |
return 0; | |
} |
# 例子:带回函数中的多个值
计算一维数组元素的平均值,并能带回数组中的最大值与最小值。
函数原型设计如下:
double faver(int s[],int n,int *max,int *min); |
其中:
s
- 一维数组n
- 数组中元素个数max
- 指向最大值min
- 指向最小值- 将平均值作为函数的返回值
double faver(int s[],int n,int *max,int *min) | |
{ | |
// 变量定义及初始化 | |
double aver=s[0]; | |
*max=*min=s[0]; | |
for(int i=1;i<n;i++) | |
{ | |
aver+=s[i]; | |
if(s[i]>*max) | |
{ | |
*max=s[i]; | |
} | |
if(s[i]<*min) | |
{ | |
*min=s[i]; | |
} | |
} | |
return aver/n; | |
} | |
int main() | |
{ | |
int a[5]={80,89,67,76,98},max,min; | |
double aver; | |
aver=faver(a,5,&max,&min); // 调用函数 | |
cout<<"max="<<max<<"\n"<<"min="<<min<<endl; | |
cout<<"aver="<<aver<<endl; | |
return 0; | |
} |
# 返回指针的函数
<类型> *<函数名>(<形式参数表>) | |
{ | |
<语句序列> | |
} |
<类型> *
- 为函数的返回值类型,是一个指针类型。
# 例子:返回字符串
编写函数,返回字符串中首次出现的非空格字符开始的字符串。
如: " using namespace std;"
返回 "using namespace std;"
// 返回字符指针的函数 | |
char *noblank(char *str) | |
{ | |
while(*str==' ') { | |
str++; | |
} | |
return str; | |
} | |
int main() | |
{ | |
char *s1=" using namespace std;", *s2; | |
s2=noblank(s1); | |
cout<<s2<<endl; | |
return 0; | |
} |
# 指向函数的指针变量
使用指向函数的指针变量可以存放函数的指针。
<函数返回值类型> (*<指针变量名>) (<形参类型表列>); |
函数名就是函数的地址,也就是函数的指针。
例如:定义指向 double 型函数的指针变量,该函数有一个 double 型参数
double (*pf)( double ); | |
pf=sqrt; //pf 指向一个平方根函数 |
这时,使用 *pf
可以调用该函数。
如:
cout<<(*pf)(2.0)<<endl; // 输出根号 2 的值,与下方表示式是等价的 | |
cout<<sqrt(2.0)<<endl; |
# 课堂讨论
- C++ 中如何通过指针访问数据。可以结合生活中的例子,说明通过指针访问数据的优点。
指针就是变量在内存中的地址,直接通过访问指针来获取变量的值。
指针的优点:
- 可以提高程序的编译效率和执行速度,使程序更加简洁。
- 通过指针,被调用函数可以向调用函数处返回除正常的返回值之外的其他数据,从而实现两者间的双向通信。
- 利用指针可以实现动态内存分配。
- 请说明函数参数的指针传递 (地址传递) 和值传递的区别。
指针传递传入的是一个指向变量的指针,可以利用这个指针直接控制变量,而值传递只是传入了变量的值,是 “只读的”,不能利用传入的值改变变量的值。
- 在函数的形参中使用指向函数的指针的好处是什么?
要传递一个很大的结构体或类对象,但是并不想在调用方法时额外申请一个对象的空间,此时可以使用指针来传递参数,此时函数内部可以通过指针访问该对象。
想在方法中修改某个输入参数的值时,此时需要借助于传址操作,传递要修改参数的指针给方法。
在实际应用中上面两个场景的目标在 C++ 中都可以使用引用来达成。
# 随堂练习
C++ 中的指针指的是 。
若有声明语句
int a, *p=&a;
,则*p=2015;
的意义是 。下列哪句是指针变量的声明?
当函数的形参是指向整型数的指针变量(如
int *p
)时,函数的实参应是 。一个函数,如果返回值是指针 ,那么这个指针应指向 。
下列哪项声明的是指向函数的指针?