C++PrimerPlus
20260128-0129
学习C++ 第7章进行查漏补缺
1.函数原型
-
定义:函数的声明,告知编译器函数名、返回类型、参数类型;便于前向调用与类型检查。
verilogvoid 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) { /*两者都不能改*/ } -
数组形参会退化为指针
verilogvoid 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值的函数的指针。
函数指针的优势
- 提高代码的灵活性和可复用性 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变量手动传递数组的有效元素数量。