以下为个人学习笔记和习题整理
课程:计算机程序设计(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 是一个指针变量。

将存放 “地址” 的变量称为指针变量,这里的 “地址” 就是指针。
因此,变量的地址就是变量的指针。

# 指针类型的主要用途

  1. 参数传递
    指针作参数可以实现参数按引用传递的功能。
  2. 动态分配
    利用动态分配可构建动态数组,动态数组需要借助指针实现
  3. 数据结构
    创建可伸缩的数据结构,如链表、棧与队列、树和图等。
  4. 多态处理
    面向对象编程中 “运行多态性” 的处理是利用指针与引用实现的。

# 指针变量的使用

变量有地址,指针变量可以存放变量的地址。
当指针变量中存放某个变量的地址后,我们就说该指针变量指向这个变量。

# 定义指针变量

即给指针变量分配内存空间。

格式
<数据类型> *<变量名>
  • * 是指针类型变量的标志符号。
  • <变量名> 为指针变量名(构成同标识符)。
  • <数据类型> 为指针变量所指向变量的数据类型。
  • <数据类型> * 表示指针类型。

如:定义一个指向字符类型的指针变量 pch

char *pch; //pch 是一个字符型指针变量

注意:变量 pch 的数据类型为 char * ,而不是 char

# 初始化

  1. 在定义指针变量的同时为指针变量提供初值。
    如: int a=5,*pta=&a;
    其中 a 的初值为 5, pta 的初值为整型变量 a 的地址。

这时,pta 与 a 的关联如下:

Error: Failed to launch the browser process! spawn /Applications/Google Chrome.app/Contents/MacOS/Google Chrome ENOENT


TROUBLESHOOTING: https://github.com/puppeteer/puppeteer/blob/main/docs/troubleshooting.md

即,指针变量 pta 指向变量 apta->a

  1. 使用赋值语句为变量提供初始值
    上述定义语句: int a=5,*pta=&a;
    与下面语句组的功能是等效的。
int a,*pta; // 先定义变量
a=5; // 使用赋值语句提供初值
pta=&a; // 使用赋值语句提供初值

注意: pta=&a; 不可写成: *pta=&a;
因为, *pta 并不表示指针变量 pta ,而表示 pta 所指向的变量 a
指针变量指针变量所指向的变量是两个完全不同的概念。

# 定义多个

double *p1,*p2;

定义 2 个双精度型的指针变量 p1p2 ,它们只能指向 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

注意:

  1. *p*(&a) 等价,即就是 a
    p 是一个指针变量,而 &a 是一个指针常量。
  2. 指针变量的值一定是 “地址”;指针变量所指对象的值不一定是 “地址”。

# 使用注意点

  • 不要访问没有被初始化的指针变量。
    如:
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;

# 课堂讨论

  1. C++ 中如何通过指针访问数据。可以结合生活中的例子,说明通过指针访问数据的优点。

指针就是变量在内存中的地址,直接通过访问指针来获取变量的值。
指针的优点:

  1. 可以提高程序的编译效率和执行速度,使程序更加简洁。
  2. 通过指针,被调用函数可以向调用函数处返回除正常的返回值之外的其他数据,从而实现两者间的双向通信。
  3. 利用指针可以实现动态内存分配。
  1. 请说明函数参数的指针传递 (地址传递) 和值传递的区别。

指针传递传入的是一个指向变量的指针,可以利用这个指针直接控制变量,而值传递只是传入了变量的值,是 “只读的”,不能利用传入的值改变变量的值。

  1. 在函数的形参中使用指向函数的指针的好处是什么?

要传递一个很大的结构体或类对象,但是并不想在调用方法时额外申请一个对象的空间,此时可以使用指针来传递参数,此时函数内部可以通过指针访问该对象。
想在方法中修改某个输入参数的值时,此时需要借助于传址操作,传递要修改参数的指针给方法。
在实际应用中上面两个场景的目标在 C++ 中都可以使用引用来达成。

# 随堂练习

  1. C++ 中的指针指的是

    • 存储单元的地址
    • 存在存储单元中数据
    • 名字叫 pointer 的变量
    • 变量名的别称
  2. 若有声明语句 int a, *p=&a; ,则 *p=2015; 的意义是

    • 给指针变量 p 赋值
    • 设置指针变量 p 的地址
    • 相当于 p=p*2015;
    • 相当于 a=2015;
  3. 下列哪句是指针变量的声明?

    • char s;
    • char *p;
    • *p=&s;
    • *p='a';
  4. 当函数的形参是指向整型数的指针变量(如 int *p )时,函数的实参应是

    • 整型变量
    • 整型常量
    • 整型表达式
    • 整型变量的地址
  5. 一个函数,如果返回值是指针 ,那么这个指针应指向

    • 主调函数中的变量
    • 函数中的变量
    • 函数中的指针
    • 函数的形参
  6. 下列哪项声明的是指向函数的指针?

    • char *p;
    • char a,char &p=a;
    • char (*p)(char *,char *);
    • char *p[10];
阅读次数

请我喝[茶]~( ̄▽ ̄)~*

Ruri Shimotsuki 微信支付

微信支付

Ruri Shimotsuki 支付宝

支付宝

Ruri Shimotsuki 贝宝

贝宝