C++PrimerPlus学习笔记之第7章 函数与指针的关系

C++PrimerPlus

20260128-0129

学习C++ 第7章进行查漏补缺

1.函数原型

  • 定义:函数的声明,告知编译器函数名、返回类型、参数类型;便于前向调用与类型检查。

    verilog 复制代码
    void log(std::string msg, int level = 1); // 原型中给默认值
  • 语法:返回类型 函数名(参数列表);

    • 参数名可省略:int add(int, int);
    • 无参:void f();void f(void);
  • 参数要点:

    • 值传递:不影响实参;引用传递 T& 会影响实参,常用 const T& 传大对象

      verilog 复制代码
      例如:
      传递一个指针 int *p;
      void f1(int* p) { *p = 10; }            // OK
      void f2(const int* p) { *p = 10; }      // 编译错误:不能改所指内容
      void f3(int* const p) { p = nullptr; }  // 编译错误:不能改指针本身
      void f4(const int* const p) { /*两者都不能改*/ }
    • 数组形参会退化为指针

      verilog 复制代码
      void foo(int* arr, int );
      void foo(int arr[],int );
      void foo(int [],int );  三者一样
  • 默认参数:只在声明处给;从右向左连续提供:double power(double x, int n = 2);

  • 常见坑:

    • 同一函数的原型不一致
    • 声明和定义重复写默认参数
    • 忘记为数组传长度参数
    • 隐式转换导致重载匹配二义性

2.指针函数

函数指针是在C++中用来指向函数地址的一种指针类型。通过函数指针,可以调用它所指向的函��,能够实现代码的更高灵活性和动态行为。

函数指针的基本形式定义如下:

cpp 复制代码
返回类型 (*函数指针名称)(参数类型列表);
  • 返回类型:函数指针指向的函数的返回值类型。
  • 函数指针名称:实际表示指针变量的标识符。
  • 参数类型列表:函数指针指向的函数的参数类型列表。

例如:

cpp 复制代码
double (*funcPtr)(int);
  • 这是一个指向具有一个int参数并返回double值的函数的指针。

函数指针的优势

  1. 提高代码的灵活性和可复用性 2.实现多态 3.减少代码冗余 4.更易于维护

代码总结:

verilog 复制代码
#include<iostream>
void estimate(int, double(*pf)(int));
double f1(int);
double f2(int);
int main()
{
	int codeLine;
	std::cout << "How many lines of code do you needs?\n";
	std::cin >> codeLine;
	std::cout << "Here have f1 f2 estimate:\n";
	std::cout << "f1 estimate code Time:\n";
	estimate(codeLine, f1);
	std::cout << "f2 estimate code Time:\n";
	estimate(codeLine, f2);
	
}

double f1(int line)
{
	return line * 0.05;
}
double f2(int line)
{
	return line * 0.02;
}
void estimate(int line, double (*pf)(int code))
{
	std::cout << "estimate:\n" << (*pf)(line)<<"Hour\n";
	return;
}

3.函数和数组

函数与指针

深度解析函数指针与数组的关系

verilog 复制代码
const double* (*(ps[3]))(const double*, int) = { f1,f2,f3 };
auto psa = ps;
//等价于
const double* (**psa)(const double*,int) = ps;

解析

verilog 复制代码
ps[0] : const double* (*)(const double*, int) //数组里面的元素是  指向函数的指针
&ps[0] : const double* (**)(const double*, int)
ps = &ps[0];
ps      // 函数指针数组
psa     // 指向函数指针的指针
*psa    // 函数指针
(*psa)()// 调用函数

内存解析

verilog 复制代码
======================= 内存深度快照 (Memory Snapshot) =======================
auto psa = ps;
psa = &ps[0]----> &ps[0]= 0x5000
psa = 0x5000    *psa == ps[0] == f1 == 0x1000
(*psa)() == 跳转到 0x1000 执行代码

1. 代码段 (Code Segment) - 存放函数的实际指令
+------------------------+---------------------+-------------------------------+
|     内存中的值 (Value)   |    地址 (Address)    |           变量说明             |
+------------------------+---------------------+-------------------------------+
| [ f1 的二进制机器码 ]    |       0x1000        |  函数 f1() 的实体              |
|  55 48 89 E5 ...       |                     |  (这是 f1 的入口地址)          |
+------------------------+---------------------+-------------------------------+
| [ f2 的二进制机器码 ]    |       0x2000        |  函数 f2() 的实体              |
+------------------------+---------------------+-------------------------------+
| [ f3 的二进制机器码 ]    |       0x3000        |  函数 f3() 的实体              |
+------------------------+---------------------+-------------------------------+
           ^
           | 被 ps 数组中的元素所指向
           |

2. 栈区 (Stack) - 存放数组 ps
+------------------------+---------------------+-------------------------------+
|     内存中的值 (Value)   |    地址 (Address)    |           变量说明             |
+------------------------+---------------------+-------------------------------+
|         0x1000         |       0x5000        |  ps[0]=0x1000 &ps[0]=0x5000   |
| (指向 f1 的入口 0x1000)  |                     |   *psa = 0x5000              |
+------------------------+---------------------+-------------------------------+
|         0x2000         |       0x5008        |  ps[1]                        |
| (指向 f2 的入口 0x2000)  |                     |  *(psa + 1) 的结果            |
+------------------------+---------------------+-------------------------------+
|         0x3000         |       0x5010        |  ps[2]                        |
| (指向 f3 的入口 0x3000)  |                     |  *(psa + 2) 的结果            |
+------------------------+---------------------+-------------------------------+
           ^
           | 被 psa 变量所指向
           |

3. 栈区 (Stack) - 存放指针变量 psa
+------------------------+---------------------+-------------------------------+
|     内存中的值 (Value)   |    地址 (Address)    |           变量说明             |
+------------------------+---------------------+-------------------------------+
|         0x5000         |       0x8000        |  变量 auto psa = ps; 
| (指向 ps[0] 的地址)      |                     |  类型: const double* (**)(...)|
+------------------------+---------------------+-------------------------------+

注意:

1. 核心推导与物理本质

定义 & 为取地址,* 为读取内容。

推导链条为:auto psa = ps; psa = &ps[0]; *psa = ps[0] = &f1; (获取函数的入口地址)。

进而得出:**psa = *ps[0] = f1。

此处的 f1 物理上指代 *(&f1),即读取该地址存放的实际内容------函数的执行代码块(机器指令)

2. 调用机制与语法特性

常规调用为 f1()。因为 *psa(即 &f1)拿到了地址,所以 (*psa)() 即可完成调用。
特殊性 :C/C++ 中函数名本身即代表地址。函数实体(代码块)在表达式中会自动"退化"回函数指针。

所谓退回指针的含义就是 *f1--->f1 **********f1->f1
因此,无论是 f1() (直接用名)、(*psa)() (解引用指针) 还是 (*f1)() (对函数名解引用),编译器都会解析为跳转到该代码块执行。
在 C 语言标准中,函数名 f1 在大多数表达式中,会
自动退化(Decay)**为 指向该函数的指针。

这就导致了一个著名的"无限套娃"现象:

f1(函数名) -> 视作地址 0x1000

&f1(取地址) -> 还是地址 0x1000

*f1(解引用) -> 变成了函数实体 -> 立刻又退化为地址 0x1000

******f1 -> 还是 0x1000

所以,虽然物理上 **psa 指向的是代码块(机器指令),但在语法使用上,它表现得就像一个永远关不掉的指针。
内存内容的深度解析: 到底读到了什么?

"*为读取内容,那它读取的内容是不是代码块?"

是的!

对于 int *p:*p 读取的是内存里的二进制数据(比如 0000 0005),解释为整数 5。

对于函数指针 Fun *p:*p 读取的是内存里的二进制数据(比如 55 48 89 E5),解释为机器指令 (OpCodes)。

区别在于:

数据的二进制,你可以赋值给变量:int a = *p;

代码的二进制,你不能赋值给变量(你不能写 code c = *p),你只能让 CPU 去执行它。

4.函数和结构

cpp 复制代码
#include<iostream>
#include<string>
using namespace std;
const int SIZE = 4;
void showString(const string [],int );
int main()
{
	int i;
	string list[SIZE];
	cout << "Enter Size of string" << "\n";
	cin >> i;
	cin.get();
	// 必须把 cin >> i 留下的那个 '\n' 换行符丢弃掉
// 否则第一次 getline 会读到一个空字符串
	for (int j = 0; j < i; j++)
	{
		getline(cin, list[j]);
	}
	cout << "string size = " << list->size()<<"\n";
	showString(list,i);
	return 0;
}
void showString(const string sa[],int n)
{
	for (int i = 0; i < n; i++)
	{
		cout << i<<":" << sa[i] << "\n";
	}
}

5.函数和字符串

cpp 复制代码
#include<iostream>

struct polar
{
	double distance;
	double angle;
};
struct rect
{
	double x;
	double y;
};
void showPolar(const polar* p);
polar rectToPolar(const rect* r);
int main()
{
	using namespace std;
	polar p1 = { 10,2 };
	rect r1 = { 2,2 };
	polar s = rectToPolar(&r1);
	showPolar(&p1);
	showPolar(&s);
	while (cin >> r1.x >> r1.y)
	{
		polar n = rectToPolar(&r1);
		showPolar(&n);
		cout << "Enter two number(q to quit)\n";
	}
	cout << "Done";
	return 0;

}
void showPolar(const polar* p)
{
	std::cout << "distance = " << p->distance<<"\n";
	std::cout << "angle = " << p->angle<<"\n";
}
polar rectToPolar(const rect* r)
{
	polar temp;
	temp.distance = sqrt(r->x * r->x + r->y * r->y);
	temp.angle = atan2(r->y, r->x);
	return temp;
}

错误总结

1. 输入缓冲区残留(幽灵回车)

  • 错误现象cin >> i 后,第一次 getline 自动跳过(读入空行)。
  • 原因cin >> 只读取数值,将回车符 \n 留在了缓冲区。getline 遇到 \n 视为结束。
  • 修正 :在 cin >> 后立即调用 cin.get();cin.ignore(); 吃掉那个回车符。

2. 数组与指针的方法误用

  • 错误写法list->size()
  • 原因 :原生数组(string list[])没有 .size() 方法。list 退化为指针,list->size() 实际上是在调用 list[0].size() ,即获取第一个字符串的字符长度,而非数组的元素个数。
  • 修正 :原生数组必须通过额外的 int 变量手动传递数组的有效元素数量。
相关推荐
zhuqiyua6 小时前
第一次课程家庭作业
c++
只是懒得想了6 小时前
C++实现密码破解工具:从MD5暴力破解到现代哈希安全实践
c++·算法·安全·哈希算法
m0_736919107 小时前
模板编译期图算法
开发语言·c++·算法
玖釉-7 小时前
深入浅出:渲染管线中的抗锯齿技术全景解析
c++·windows·图形渲染
【心态好不摆烂】7 小时前
C++入门基础:从 “这是啥?” 到 “好像有点懂了”
开发语言·c++
dyyx1117 小时前
基于C++的操作系统开发
开发语言·c++·算法
AutumnorLiuu7 小时前
C++并发编程学习(一)——线程基础
开发语言·c++·学习
m0_736919107 小时前
C++安全编程指南
开发语言·c++·算法
阿猿收手吧!7 小时前
C++ std::lock与std::scoped_lock深度解析:从死锁解决到安全实践
开发语言·c++
蜡笔小马7 小时前
11.空间索引的艺术:Boost.Geometry R树实战解析
算法·r-tree