C++ Primer Plus 学习笔记(四)

第七章. 函数—C++ 的编程模块

1. 复习函数的基本知识

没有返回值的函数结尾可以选择写 "return;",也可以不写 “return;”。有返回值的函数,返回值可以自动强制转换为函数相应的返回值类型(如函数返回类型是 double,而函数返回一个 int 值的表达式,该 int 值会被强制转换为 double)。

函数的返回值通过复制到指定的 CPU 寄存器或内存单元,来进行调用。

函数原型中的变量名可以与定义时的变量名不同,原型中的变量名相当于占位符。

函数原型帮助编译器:1. 正确处理函数返回值;2. 确保函数参数正确(数目和类型)。

与 C 语言不同,C++ 中函数原型是必不可少的。

2. 函数参数和按值传递

函数定义中的参数,用来接收传递值,称为形参( parameter ),传递给函数的值称为实参( argument )。当函数被调用时,计算机分配内存并创建形参,函数结束后自动释放该内存。计算组合时,可以定义函数:

long double combination(unsigned numbers, unsigned picks) {
    long double result = 1.0;
    long double n = 1.0;
    long double p = 1.0;
    
    for (n = numbers, p = picks; p > 0; p--, n--) {
        result *= n / p;
    }
    
    return result;
}

在实际调用函数时,计算机会创建一个实参的副本。

3. 函数和数组

对数组名 用地址运算符 & 时,返回的是整个数组的地址。

C++中,当且仅当用于函数头或者函数原型中 int *p 和 int p[] 含义相同,都表示 p 是一个指向 int 类型的指针。 

两指针相减(讨论都指向同一数组的情况),值是两个指针指向的元素索引的差值, 也就是两个指针的差值除以对应类型 sizeof 的值。

const 修饰指针的两种形式:

int age = 19;
const int * ptr1 = &age;  // ptr1 是一个指向常量的指针
int * const ptr2 = &age;  // ptr2 是一个指针常量

指向常量的指针:

1. 不能通过指针的解引用更改值,但是 age 可以赋其他的值;2. 可以指向 const 的常量或非 const 的变量,而无 const 的指针只能指向非 const 的变量;3. 可以在一级指针之间,将指向常量的指针和非 const 指针之间赋值,但是不要涉及二级指针;4. 可以指向其他的变量。

*ptr1 = 20;  // 错误
age = 20;  // 可以
int *ptr3;
const int year = 4;
ptr3 = &year;  // 错误
ptr3 = ptr1;  // 可以
*ptr3 = 20;  // 可以

指针常量:表示该指针只能指向这个变量,即指针本身的值不能改变,也就是只能指向这一块地址(ptr2 只能指向 age),但是可以通过解引用改变值。

4. 函数和二维数组

形参是二维数组名的函数原型:

int data[3][4] = {{1,2,3,4},
                  {5,6,7,8},
                  {9,1,2,3}};
int total = sum(data, 3);

int sum(int (*arr)[4], int n);  // 可以
int sum(int arr[][4], int n);  //可以
int sum(int arr[3][4], int n);  //可以
int sum(int arr[][], int n);  //不可以

即可以省略“行“数,不能省略“列”数。省略列,会有多种数组可能。比如:

有{1, 2,3,4,5,6,7,8}组成二位数组,a[3][]和a[][3]两种情况,a[][3] = {{1,2,3},{4,5,6},{7,8,0}};而a[3][] = {{1,2,3},{4,5,6},{7,8,0}} 或者 {{1,2,3,4},{5,6,7,8},{0,0,0,0}} 或者 {{1,2,3,4,5,6},{7,8,0,0,0,0},{0,0,0,0,0,0}}等等,因此,省略列无法确定数组,而省略行可以。

5. 函数和C—风格字符串

字符串以 ‘\0’ 结尾,函数处理字符串使用 char* ,字符指针作为函数形参。字符串地址跟数组类似,地址与第一个字符地址相同。

6. 函数和结构

与数组不同,相同类型的结构可以直接赋值,在函数中,结构就相当于一般的变量使用。

7. 函数和string对象

使用 string 类需要包含头文件<string>,string类在 std 命名空间中。string 类对象的使用和 int 等一般类型的对象类似。

8. 函数与 array 对象

使用 array 类 要包含头文件 <array>,位于 std 命名空间中。

#include <array>
const int Seasons = 4;
const std::array<std::string, Seasons> Snames =
    {"Spring", "Summer", "Fall", "Winter"};

void fill(std::array<double, Seasons> *pa);  // array对象指针形参
void show(std::array<double, Seasons> da);  // array对象形参

void fill(std::array<double, Seasons> *pa) {
    using namespace std;
    for (int i = 0; i < Seasons; i++) {
        cout << "Enter " << Snames[i] << " expenses: ";
        cin >> (*pa)[i];
    }
}
void show(std::array<double, Seasons> da) {
    using namespace std;
     for (int i = 0; i < Seasons; i++) {
        cout << da[i];
    }
}

使用时,将 da 当做数组名就可以了。pa指向 array 对象,所以 *pa 就可以当做数组名使用。

9. 递归

C++ 不允许 main() 调用自身,C 可以。当递归调用结束后,会继续运行当前函数后面的语句,当前函数结束后,控制权会返回到上一级递归。

#include <iostream>
void countdown(int n);

int main()
{
    countdown(4);
    return 0;
}

void countdown(int n)
{
    using namespace std;
    cout << "counting down ... : "<< n << " (n at " <<
            &n << ")" << endl;
    if (n > 0)
    {
        countdown(n - 1);
    }
    cout << n << ": Kaboom;" << "        (n at " <<
          &n << ")" << endl;
}  

输出:

counting down ... 4 (n at 0012FE0C)
counting down ... 3 (n at 0012FD34)
counting down ... 2 (n at 0012FC5C)
counting down ... 1 (n at 0012FB84)
counting down ... 0 (n at 0012FAAC)
0: Kaboom!          (n at 0012FAAC)  
1: Kaboom!          (n at 0012FB84)
2: Kaboom!          (n at 0012FC5C)
3: Kaboom!          (n at 0012FD34)
4: Kaboom!          (n at 0012FE0C)

函数每次调用都会创建同名的不同变量,在同级调用的函数中的同名变量地址是相同的。

10. 函数指针

函数名表示函数地址(think(),think 就是函数地址),函数名做参数表示使用函数地址,函数名加括号作为参数,表示使用函数返回值。

process(think);  // think函数的地址为参数
thought(think())  // think函数的返回值为参数

声明指向函数的指针,需要指明函数的返回类型,以及函数的特征标(参数列表)。

double pam(int);  // 函数声明
double (*pf)(int);  //声明指向pam函数的函数指针

就是将函数原型中的函数名改为(*指针变量名),括号不能省略,不然会变成声明一个返回double 指针的函数pf。声明完函数指针,可以将函数地址赋给指针。

pf = pam;

要将函数地址作为另一个函数的形参,可以:

void estimate(int lines, double (*pf) (int));

函数指针可以直接解引用当做函数名使用:

double x = (*pf)(4);  // x = pam(4)
double y = pf(5);  // y = pam(5)  不解引用也合法

定义函数指针数组:

//  f1 f2 f3函数的形参含义完全一样
const double * f1(const double ar[], int n);
const double * f2(const double [], int);
const double * f1(const double *ar, int);
const double *(*p1)const double *ar, int) = f1;  //p1是指向函数f1的函数指针
auto p2 = p1;  // 应用auto推断功能,p2也是指向f1的指针

//创建函数指针数组p3
const double *(*p3[3])(const double *ar, int) = {f1, f2, f3};
auto p4 = p3;  // 也可以 const double * (**pb)(const double *ar, int) = p3;

//创建函数指针数组的数组指针:p5, pa
const double *(*(*p5)[3])(const double *ar, int) = &p4;
auto pa = &p3;

auto 的自动类型推断只能用于单值初始化,不能用于初始化列表,数组名是数组第一个元素的指针常量,所以 p4 = p3,p4的值也数组 p3 第一个元素的指针的值。定义函数指针数组的数组指针时,(*p5)[3]声明一个数组指针,(*p5)[3]说明数组的元素是指针,(*(*p5)[3])说明数组的元素都是函数指针的指针,剩下的说明函数指针指向的函数的返回值类型及形参类型。要调用数组第一个元素,&p4要解引用两次。

也可以利用 typedef 创建将函数指针的类型起别名:

//p_fun 是指向f1 f2 f3 函数指针的类型别名
typedef const double * (*p_fun)(const double ar[], int n);
p_fun pa[3] = {f1, f2, f3};  // pa 是函数指针数组
p_fun (*pd)[3] = &pa;  // pd 是pa的数组指针
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值