《C++ primer plus》学习笔记——第七章C++函数的编程模块

文章目录

  • 一、函数的基本知识
    • 1.函数的定义:function definition
      • 1)**没有返回值的函数**:称之为void函数
      • 2)**有返回值类型的函数**
    • 2.函数原型function prototype和函数调用function call
  • 二、函数参数和按值传递
    • 1.多个参数的传递
    • 2.另外一个接受两个参数的函数
    • 3.一个用自下而上方式的模块开发的数组函数例子
        • b)C++将声明const double ar[]解释为const double* ar ,so,该声明实际上是说,ar指向的是一个常量值。
    • 4.使用数组区间的函数
    • 5.指针和const
  • 三、函数和二维数组
  • 四、函数和C风格字符串
      • 1.将C风格字符串作为参数的函数
        • (2)也可以在函数头中**使用数组表示法**,而不声明str。如下所示
      • 2.返回C风格字符串的函数
  • 五、函数和结构
    • 1.传递和返回结构(按值传递)
    • 2.另一个处理结构的函数示例(值传递)
    • 3.传递结构的地址
  • 六、函数与string对象
  • 七、函数与array对象(模板类array的数组)
  • 八、递归
    • 1.包含一个递归调用的递归
    • 2.包含多个递归调用的递归
  • 九、函数指针
    • 1.函数指针的基础知识
    • 2.函数指针的使用例子
    • 3.深入函数指针
    • 4.使用typedef简化
  • 十、大总结

一、函数的基本知识

1)CPP自带一个包含函数的大型库(标准ANSI库+多个CPP库)
但真正的编程乐趣在于编写自己的函数;要提高编程效率,还要深入地学习STL和BOOST C++提供的功能。

2)要使用CPP函数,必须要处理好:定义,提供原型(也就是声明)和调用。

3)库函数是已经定义和编译好的函数,同时可以使用标准库头文件提供其原型,因此,只需要正确地调用这种函数即可。

eg:

#include
#include

// calling.cpp -- defining, prototyping, and calling a function
#include 

void simple();    // function prototype

int main()
{
    using namespace std;
    cout << "main() will call the simple() function:\n";
    simple();     // function call
	cout << "main() is finished with the simple() function.\n";
    // cin.get();

	system("pause");
	return 0;
}

// function definition
void simple()
{
    using namespace std;
    cout << "I'm but a simple function.\n";
}

《C++ primer plus》学习笔记——第七章C++函数的编程模块_第1张图片

说明:
在每个函数定义中,都使用了一条using编译指令,因为每个函数都使用了cout。或者,在函数定义前放置一条using编译指令或在函数中使用std::cout。

1.函数的定义:function definition

函数可分为两类:没有返回值的函数和有返回值的函数。

1)没有返回值的函数:称之为void函数

通用格式如下:
《C++ primer plus》学习笔记——第七章C++函数的编程模块_第2张图片
说明:
parameterList指定了传递给函数的参数类型和数量;
可选的返回语句标记了函数的结尾;

eg:cheers()函数将打印指定次数n的函数,如下:
《C++ primer plus》学习笔记——第七章C++函数的编程模块_第3张图片

2)有返回值类型的函数

通用格式如下:
《C++ primer plus》学习笔记——第七章C++函数的编程模块_第4张图片

说明:
a)对于有返回值的函数,必须使用返回语句,以便将值返回给调用函数,值的本身可以是常量、变量、也可以是表达式,但是其结果类型必须是typeName类型或可以被转换为typeName。

b)CPP对于返回值的类型有一定的限制:不能是数组,但是可以是其他任何类型。

c)函数的返回值机制如下所示:
实际的过程说明(了解即可,写代码的话看下面的图去理解更好):
函数通过将返回值复制到指定的CPU寄存器或内存单元中,来将其返回;
随后,调用程序将查看该内存单元;
返回函数和调用函数必须就该内存单元中所存储的数据的类型达成一致;

《C++ primer plus》学习笔记——第七章C++函数的编程模块_第5张图片

d)若函数中包含多条返回语句,则:函数在执行遇到的第一条返回语句后结束。
《C++ primer plus》学习笔记——第七章C++函数的编程模块_第6张图片

2.函数原型function prototype和函数调用function call

函数原型经常被隐藏在include文件中

eg:

#include
#include

// protos.cpp -- using prototypes and function calls
#include 
void cheers(int);       // prototype: no return value
double cube(double x);  // prototype: returns a double
int main()
{
    using namespace std;
    cheers(5);          // function call
    cout << "Give me a number: ";
    double side;
    cin >> side;
    double volume = cube(side);    // function call
    cout << "A " << side <<"-foot cube has a volume of ";
    cout << volume << " cubic feet.\n";
    cheers(cube(2));    // prototype protection at work
    // cin.get();
    // cin.get();

	system("pause");
	return 0;
}

void cheers(int n)
{
    using namespace std;
    for (int i = 0; i < n; i++)
        cout << "Cheers! ";
    cout << endl;
}

double cube(double x)
{
    return x * x * x; 
}

《C++ primer plus》学习笔记——第七章C++函数的编程模块_第7张图片

说明:
1)程序中,只要使用了名称空间std中成员,就会使用编译指令using。

2)
在这里插入图片描述

3)在CPP中,函数原型的重要性
原型描述了函数到编译器的接口,也就是说,它将函数返回值的类型以及参数的类型和数量告诉编译器。

其中,为了避免使用函数原型的唯一方法是:在首次使用函数之前定义它,**但这并不总是可行的。**另外,CPP的编程风格是将main()放在最前面,因为它通常了程序的整体结构。

4)函数原型的语法如下:
a)函数原型是一条语句,所以,必须以分号结束;
b)获得原型最简单的方法是:复制函数定义中的函数头,并添加分号;
eg:
在这里插入图片描述
c)在函数原型中,可以不要提供变量名,有类型列表就足够了。
eg:
对于cheer()原型,该函数只提供了参数类型:
在这里插入图片描述

5)C++原型与ANSI原型的区别
为了与基本的C语言兼容,ANSI C中的原型是可选的,而在C++中,原型是必不可少的。

6)原型的功能
a)原型确保了以下几点:
编译器能够正确处理函数返回值;
编译器能检查所使用的参数数目是否正确;
编译器检查使用的参数类型是否正确。如果不正确,则转换为正确的类型(如果可能的话)。

b)如何参数的数目不对,将发生什么情况
CPP自动将传递的值转换为原型中指定的类型,条件是:两者都是算术类型。
在编译阶段进行的原型化被称之为:静态类型检查static type checking
eg:
在这里插入图片描述

说明:
《C++ primer plus》学习笔记——第七章C++函数的编程模块_第8张图片

二、函数参数和按值传递

1)C++通常按照值来传递参数。
eg:
《C++ primer plus》学习笔记——第七章C++函数的编程模块_第9张图片
说明:
用于接收传递值的被称之为:形参parameter;
传递给函数的值被称之为:实参 argument;

eg:按值传递
《C++ primer plus》学习笔记——第七章C++函数的编程模块_第10张图片

eg:局部变量的具体eg如下:
如果在main()中声明了一个名为x的变量,同时在另外一个函数中也声明了一个名为x的变量,这俩是完全不同的,毫无关系的变量。这样的变量称之为自动变量,因为他们是在程序执行过程中自动分配和释放的
在这里插入图片描述

1.多个参数的传递

1)在定义函数的时候,可以在函数头中使用逗号分隔的参数声明列表:
在这里插入图片描述
说明:
函数n_chars()接受一个char参数和一个int参数。
如果函数的两个参数的类型相同,则必须分别指定每个参数的类型。
在这里插入图片描述

2)只需添加分号就可以得到该函数的原型
在这里插入图片描述

3)原型中的变量名不必与定义中额变量名相同,甚至可以省略
在这里插入图片描述

eg:
在这里插入图片描述

#include
#include

// twoarg.cpp -- a function with 2 arguments
#include 
using namespace std;
void n_chars(char, int);
int main()
{
    int times;
    char ch;

    cout << "Enter a character: ";
    cin >> ch;
    while (ch != 'q')        // q to quit
    {
        cout << "Enter an integer: ";
        cin >> times;
        n_chars(ch, times); // function with two arguments
        cout << "\nEnter another character or press the"
                " q-key to quit: ";
           cin >> ch;
    }
    cout << "The value of times is " << times << ".\n";
    cout << "Bye\n";
    // cin.get();
    // cin.get();

	system("pause");
	return 0;
}

void n_chars(char c, int n) // displays c n times
{
    while (n-- > 0)         // continue until n reaches 0
        cout << c;
}

《C++ primer plus》学习笔记——第七章C++函数的编程模块_第11张图片
说明:
1)用while循环提供重复输入
这里使用cin>>ch,而不是cin.get(ch)或ch=cin.get()来读取一个字符,why?
cin>>ch对空格和换行符敏感,,所以如果对上面的代码输入多个字符,会出现运行问题;
cin.get(ch)或ch=cin.get() 会读取所有的输入字符,包括空格和换行符。
cin>>ch可以轻松跳过由Enter键造成的换行符,而**cin.get()**将读取后面的换行符。

2)
在这里插入图片描述

2.另外一个接受两个参数的函数

《C++ primer plus》学习笔记——第七章C++函数的编程模块_第12张图片
在这里插入图片描述

eg:在probability()函数中使用了这个公式

#include
#include

// lotto.cpp -- probability of winning
#include 
// Note: some implementations require double instead of long double
long double probability(unsigned numbers, unsigned picks);
int main()
{
    using namespace std;
    double total, choices;
    cout << "Enter the total number of choices on the game card and\n"
            "the number of picks allowed:\n";
    while ((cin >> total >> choices) && choices <= total)
    {
        cout << "You have one chance in ";
        cout << probability(total, choices);      // compute the odds
        cout << " of winning.\n";
        cout << "Next two numbers (q to quit): ";
    }
    cout << "bye\n";
    // cin.get();
    // cin.get();

	system("pause");
	return 0;
}

// the following function calculates the probability of picking picks
// numbers correctly from numbers choices
long double probability(unsigned numbers, unsigned picks)
{
    long double result = 1.0;  // here come some local variables
    long double n;
    unsigned p;

    for (n = numbers, p = picks; p > 0; n--, p--)
        result = result * n / p ; 
    return result;
}

《C++ primer plus》学习笔记——第七章C++函数的编程模块_第13张图片
说明:
形参与其它局部变量的主要区别
形参从调用probability()的函数那里获得自己的值,而其它变量是从函数中获得自己的值。

3.一个用自下而上方式的模块开发的数组函数例子

1)编写特定的函数来处理特定的数据操作是有好处的,可靠性高、修改和调试也方便。

2)构思程序的时候,将存储属性与操作结合起来,这就是OOP思想的重要一步。

具体的所要写的eg说明如下:
《C++ primer plus》学习笔记——第七章C++函数的编程模块_第14张图片

第一步,填充数组的函数
1)编写要点1如下:
《C++ primer plus》学习笔记——第七章C++函数的编程模块_第15张图片
所以,该函数的原型如下:
在这里插入图片描述
说明:
该函数接收两个参数,一个是数组名,另一个是要读取的最大元素数;
返回的是:实际读取的元素数。

2)编写要点2如下:如何提早结束循环?
在这里插入图片描述

编写的代码如下:
《C++ primer plus》学习笔记——第七章C++函数的编程模块_第16张图片

说明:
a)当cin.clear()之后,会进入while(cin.get()!=’\n’)循环,但是cout语句依然会执行,依然会跳出,也就是说continue语句对cout和break没办法,这俩语句是会执行的,而其它的语句则不会去执行的,所以这里的while(cin.get()!=’\n’)循环就算去掉,也是可以做到和原函数一样的效果,因为最后都要直接break。
(不理解的话,我在continue那小结有eg说明)

b)如果你连续输入Enter,而不输入相应的值,,则会一直在cin>>temp;这句话这里。会出现的情况如下:
《C++ primer plus》学习笔记——第七章C++函数的编程模块_第17张图片

c)循环完成的最后一项工作是将i加1,因此循环结束后,i将比最后一个数组的索引大1,即等于填充的元素数目。

第二步,显示数组及用const保护数组的函数
1)编写要点1如下:
创建显示数组:只需将数组名和填充的元素数目传递给函数,然后给函数使用循环来显示每个元素。

2)编写要点2如下:声明形参的时候使用关键字const
在这里插入图片描述
所以,函数的声明如下:
在这里插入图片描述
说明:
a)指针ar指向的是常量数据,这表明:可以使用像ar[0]主要的值,但不能修改。

b)C++将声明const double ar[]解释为const double* ar ,so,该声明实际上是说,ar指向的是一个常量值。

编写的代码如下:
《C++ primer plus》学习笔记——第七章C++函数的编程模块_第18张图片

第三步,修改数组内容的函数
编写要点如下:
在这里插入图片描述
需要给函数传递3个参数:因子、数组和元素数目。

编写代码如下:
《C++ primer plus》学习笔记——第七章C++函数的编程模块_第19张图片
由于这个函数将修改数组的值,因此,在声明ar时,不能使用const

最后一步,将上述的代码组合起来

根据数据的存储方式(也就是数组)和使用方式(也就是3个函数)定义了数据的类型,因此,可以将它们组合成一个程序。

#include
#include

// arrfun3.cpp -- array functions and const
#include 
const int Max = 5;

// function prototypes
int fill_array(double ar[], int limit);
void show_array(const double ar[], int n);  // don't change data
void revalue(double r, double ar[], int n);

int main()
{
    using namespace std;
    double properties[Max];

    int size = fill_array(properties, Max);
    show_array(properties, size);
    if (size > 0)
    {
        cout << "Enter revaluation factor: ";
        double factor;
        while (!(cin >> factor))    // bad input
        {
            cin.clear();
            while (cin.get() != '\n')
                continue;
           cout << "Bad input; Please enter a number: ";
        }
        revalue(factor, properties, size);
        show_array(properties, size);
    }
    cout << "Done.\n";
    // cin.get();
    // cin.get();

	system("pause");
	return 0;
}

int fill_array(double ar[], int limit)
{
    using namespace std;
    double temp;
    int i;
    for (i = 0; i < limit; i++)
    {
        cout << "Enter value #" << (i + 1) << ": ";
        cin >> temp;
        if (!cin)    // bad input
        {
            cin.clear();
            //while (cin.get() != '\n')
            //    continue;
           cout << "Bad input; input process terminated.\n";
           break;
        }
        else if (temp < 0)     // signal to terminate
            break;
        ar[i] = temp;
    }
    return i;
}

// the following function can use, but not alter,
// the array whose address is ar
void show_array(const double ar[], int n)
{
    using namespace std;
    for (int i = 0; i < n; i++)
    {
        cout << "Property #" << (i + 1) << ": $";
        cout << ar[i] << endl;
    }
}

// multiplies each element of ar[] by r
void revalue(double r, double ar[], int n)
{
    for (int i = 0; i < n; i++)
        ar[i] *= r;
}

《C++ primer plus》学习笔记——第七章C++函数的编程模块_第20张图片

《C++ primer plus》学习笔记——第七章C++函数的编程模块_第21张图片

说明:
a)上面的输出情况,就是对我前面在设计函数的时候,一些解释的印证。

b)OOP设计思想:自下而上的程序设计,设计过程从组件到整体进行;
传统的过程性编程倾向于:从上而下的程序设计,首先指定模块化设计方案,然后再研究细节。
这两种方法都很有用,最终的产品都是模块化程序

c)数组处理函数的常用编写方式:
在这里插入图片描述

要点:ar实际上是一个指针,指向传输的数组的第一个元素;
另外,由于通过参数传递了元素数,这俩函数都可以使用任何长度的数组,只要数组的类型为double;

4.使用数组区间的函数

1.对于处理数组的C++函数,总共有两种方法:
1)传统方法:将指向数组起始处的指针作为一个参数,将数组长度作为第二个参数(指针指出数组的位置和数据类型)
2)指定元素区间:可以通过传递两个指针来完成:一个指针标识数组的开头,一个指针标识数组的结尾。

eg:用两个指针来指定区间

#include
#include

// arrfun4.cpp -- functions with an array range
#include 
const int ArSize = 8;
int sum_arr(const int * begin, const int * end);
int main()
{
    using namespace std;
    int cookies[ArSize] = {1,2,4,8,16,32,64,128};
//  some systems require preceding int with static to
//  enable array initialization

    int sum = sum_arr(cookies, cookies + ArSize);
    cout << "Total cookies eaten: " << sum <<  endl;
    sum = sum_arr(cookies, cookies + 3);        // first 3 elements
    cout << "First three eaters ate " << sum << " cookies.\n";
    sum = sum_arr(cookies + 4, cookies + 8);    // last 4 elements
    cout << "Last four eaters ate " << sum << " cookies.\n";
    // cin.get();

	system("pause");
	return 0;
}

// return the sum of an integer array
int sum_arr(const int * begin, const int * end)
{
    const int * pt;
    int total = 0;

    for (pt = begin; pt != end; pt++)
        total = total + *pt;
    return total; 
}

《C++ primer plus》学习笔记——第七章C++函数的编程模块_第22张图片

说明:
1)double data[20];
指针data和data+20定义了区间;
数组名data指向第一个元素,data+19指向最后一个元素,即data[19];
data+20指向了数组结尾后面的一个位置。

2)对于下面的for循环而言
for (pt = begin; pt != end; pt++)
total = total + *pt;

当pt=end的时候,它将指向区间中最后一个元素后面的一个位置,此时循环将结束。

3)注意:不同的函数调用是如何指定数组中的不同区间的
《C++ primer plus》学习笔记——第七章C++函数的编程模块_第23张图片
(a)指针cookies+ArSize指向最后一个元素后面的一个位置(数组有ArSize个元素,因此cookies[ArSize-1]是最后一个元素)
(b)区间[cookies,cookies+ArSize]指的是整个数组
(c)代码中的end-begin是一个整数值,等于数组的元素数目

5.指针和const

可以用两种不同的方式将const关键字用于指针
1)第一种:让指针指向一个常量对象,用以防止该指针修改所指向的值。
eg1:如果
在这里插入图片描述
*pt的值为const,不能被修改,具体表现如下所示
在这里插入图片描述

所以,可以将一个新地址赋给pt,
《C++ primer plus》学习笔记——第七章C++函数的编程模块_第24张图片

2)第二种:将指针本身声明为常量,这样可以防止改变指针指向的位置。

eg:
在这里插入图片描述
这种声明使得finger只能指向sloth,但允许使用finger来修改sloth的值。
中间的声明不允许使用ps来修改sloth的值,但允许ps指向另一个位置。

第一种和第二种的例子可以直接看下面这个图
《C++ primer plus》学习笔记——第七章C++函数的编程模块_第25张图片


3)如果数据类型本身并不是指针,则可以将const数据或者非const数据的地址赋给指向const的指针
eg1:
《C++ primer plus》学习笔记——第七章C++函数的编程模块_第26张图片

eg2:
《C++ primer plus》学习笔记——第七章C++函数的编程模块_第27张图片

eg3:
在这里插入图片描述
在这里插入图片描述

4)尽可能地使用const:将指针参数申明为指向常量数据的指针有两条理由:
(a)可以避免由于无意间修改数据而导致的编程错误
(b)使用const使得函数能够处理const和非const实参,否则将只能接收非const数据所以,如果条件允许,则应将指针形参声明为指向const的指针。
(c)通常将指针作为函数参数来传递时,可以使用指向const的指针来保护数据,这种作为函数参数传递必须是基本数据类型,不服?二维数组就不是基本数据类型。

《C++ primer plus》学习笔记——第七章C++函数的编程模块_第28张图片

三、函数和二维数组

1)编写二维数组作为参数的函数,必须牢记数组名被作为地址,所以相应的形参是一个指针。

eg:
在这里插入图片描述
《C++ primer plus》学习笔记——第七章C++函数的编程模块_第29张图片

sum()函数的原型如下所示,
在这里插入图片描述
<——>还有另外一种格式,该格式与上述原型的含义完全相同,可读性更强(?是不是和一维数组很相似呢?)
在这里插入图片描述
上述两个原型都指出,ar2是指针而不是数组,指针类型指出,它指向由4个int组成的数组。而且指针类型都指定了列数,这就是没有将列数作为独立的函数参数进行传递的原因。

2)注意数组指针指针数组区别
int (*ar2)[4]:指向由4个int组成的数组的指针
int *ar2[4]:由4个指向int的指针组成的数组

3)由于参数ar2是指向数组的指针,最简单的方法是将ar2看作是一个二维数组的名称,我们可以将sum函数定义如下,
《C++ primer plus》学习笔记——第七章C++函数的编程模块_第30张图片
在这里插入图片描述
说明:
(a)行数被传递给size参数,列数都是固定的——4列
(b)
在这里插入图片描述
除此之外,它还<——>等价于以下,
《C++ primer plus》学习笔记——第七章C++函数的编程模块_第31张图片
注意:
sum()的代码在声明参数ar2时,没有使用const,因为这种技术只能用于指向基本类型的指针,因为ar2是指向指针的指针。

四、函数和C风格字符串

1.将C风格字符串作为参数的函数

假设要将字符串作为参数传递给函数,则表示字符串的方式有三种:
(a)char数组
(b)用双引号括起来的字符串常量(也称之为字符串字面值);
(c)被设置为字符串的地址的char指针
上面的3种选择的类型都是char指针(即char*)

eg:
《C++ primer plus》学习笔记——第七章C++函数的编程模块_第32张图片
说明:
(a)将字符串作为参数来传递,但实际传递的是字符串第一个字符的地址
(b)字符串函数原型应该将字符串的形参声明为char*类型。
(c)C风格字符串与常规char数组之间的一个重要区别是,字符串有内置的结束字符。这意味着不必将字符串长度作为参数传递给函数,而函数可以使用循环依次检查字符串中的每个字符,直到遇到结尾的空值字符为止。

eg:
在这里插入图片描述

#include
#include

// strgfun.cpp -- functions with a string argument
#include 
unsigned int c_in_str(const char * str, char ch);
int main()
{
    using namespace std;
    char mmm[15] = "minimum";    // string in an array
// some systems require preceding char with static to
// enable array initialization

    char *wail = "ululate";    // wail points to string

    unsigned int ms = c_in_str(mmm, 'm');
    unsigned int us = c_in_str(wail, 'u');
    cout << ms << " m characters in " << mmm << endl;
    cout << us << " u characters in " << wail << endl;
    // cin.get();

	system("pause");
	return 0;
}

// this function counts the number of ch characters
// in the string str
unsigned int c_in_str(const char * str, char ch)
{
    unsigned int count = 0;

    while (*str)        // quit when *str is '\0'
    {
        if (*str == ch)
            count++;
        str++;        // move pointer to next char
    }
    return count; 
}


《C++ primer plus》学习笔记——第七章C++函数的编程模块_第33张图片

说明:
(1)c_in_str函数不应该修改原始字符串,所以它在声明形参str的时候使用了限定符const。

(2)也可以在函数头中使用数组表示法,而不声明str。如下所示

在这里插入图片描述
使用指针表示法注意,参数不一定必须是数组名,也可以是其它形式的指针。
(3)处理字符串中字符的标准方式是:
《C++ primer plus》学习笔记——第七章C++函数的编程模块_第34张图片
在这里插入图片描述

2.返回C风格字符串的函数

1)函数无法返回一个字符串,但是可以返回字符串的地址,这样的效率更高

eg:
在这里插入图片描述

#include
#include

// strgback.cpp -- a function that returns a pointer to char
#include 
char * buildstr(char c, int n);     // prototype
int main()
{
    using namespace std;
    int times;
    char ch;

    cout << "Enter a character: ";
    cin >> ch;
    cout << "Enter an integer: ";
    cin >> times;
    char *ps = buildstr(ch, times);
    cout << ps << endl;
    delete [] ps;                   // free memory
    ps = buildstr('+', 20);         // reuse pointer
    cout << ps << "-DONE-" << ps << endl;
    delete [] ps;                   // free memory
    // cin.get();
    // cin.get();

	system("pause");
	return 0;
}


// builds string made of n c characters
char * buildstr(char c, int n)
{
    char * pstr = new char[n + 1];
    pstr[n] = '\0';         // terminate string
    while (n-- > 0)
        pstr[n] = c;        // fill rest of string
    return pstr; 
}

《C++ primer plus》学习笔记——第七章C++函数的编程模块_第35张图片

说明:
(a)要创建包含n个字符的字符串,需要能够存储n+1个字符的空间,以便能够存储空值字符。
(b)因为程序中的函数请求分配n+1个字节的内存来存储该字符串,并将最后一个字节设置为空值字符,然后从后向前对数组进行填充。

关键程序的意思:循环将循环n次,直到n减少到0,最终可以填充n个元素(从后向前填充
while(n–>0)
pstr[n]=c;
在这里插入图片描述

(c)从前向后填充的方式:
在这里插入图片描述

(d)变量pstr的作用域为buildstr函数内。因此,函数结束的时候,pstr(不是字符串)使用的内存将被释放。但由于函数返回了pstr的值,因此程序仍可以通过main()中的指针ps来访问新建的字符串。当字符串不再需要的时候,程序使用delete释放该字符串占用的内存,然后将ps指向下一个字符串分配的内存块,然后分配他们。

五、函数和结构

(1)为结构编写函数比为数组编写函数要简单的多。
(2)函数也可以返回结构,与数组不同的是,结构名只是结构的名称,要获得结构的地址,必须使用地址运算符&
(3)结构传递值的方式:按值传递按地址传递按引用传递(第八章)
在这里插入图片描述

1.传递和返回结构(按值传递)

(1)当结构比较小时,按值传递结构最合理。
(2)eg
在这里插入图片描述
(a)定义结构体
《C++ primer plus》学习笔记——第七章C++函数的编程模块_第36张图片
(b)返回两个这种结构的总和的sum()函数的原型如下所示,
在这里插入图片描述

(c)由上面的a和b为基础,得到程序如下
程序解释如下:
在这里插入图片描述

#include
#include

// travel.cpp -- using structures with functions
#include 
struct travel_time
{
    int hours;
    int mins;
};
const int Mins_per_hr = 60;

travel_time sum(travel_time t1, travel_time t2);
void show_time(travel_time t);

int main()
{
    using namespace std;
    travel_time day1 = {5, 45};    // 5 hrs, 45 min
    travel_time day2 = {4, 55};    // 4 hrs, 55 min

    travel_time trip = sum(day1, day2);
    cout << "Two-day total: ";
    show_time(trip);

    travel_time day3= {4, 32};
    cout << "Three-day total: ";
    show_time(sum(trip, day3));
    // cin.get();

	system("pause");
	return 0;
}


travel_time sum(travel_time t1, travel_time t2)
{
    travel_time total;

    total.mins = (t1.mins + t2.mins) % Mins_per_hr;
    total.hours = t1.hours + t2.hours +
                 (t1.mins + t2.mins) / Mins_per_hr;
    return total;
}

void show_time(travel_time t)
{
    using namespace std;
    cout << t.hours << " hours, "
         << t.mins << " minutes\n";
}

《C++ primer plus》学习笔记——第七章C++函数的编程模块_第37张图片

说明:
《C++ primer plus》学习笔记——第七章C++函数的编程模块_第38张图片

2.另一个处理结构的函数示例(值传递)

(1)eg:编写一个将直角坐标转换为极坐标的函数
准备工作如下:
(a)直角坐标的结构:x表示水平偏移量、y表示垂直偏移量
《C++ primer plus》学习笔记——第七章C++函数的编程模块_第39张图片
(b)极坐标的结构:距离和方向

(c)为了便于显示,将弧度制转换为角度值,即将弧度制乘以180/pai——约为57.29577951,
《C++ primer plus》学习笔记——第七章C++函数的编程模块_第40张图片
在这里插入图片描述

(d)编写一个将直角坐标转化为极坐标的函数
《C++ primer plus》学习笔记——第七章C++函数的编程模块_第41张图片

编写的函数如下:

《C++ primer plus》学习笔记——第七章C++函数的编程模块_第42张图片
《C++ primer plus》学习笔记——第七章C++函数的编程模块_第43张图片

#include
#include

// strctfun.cpp -- functions with a structure argument
#include 
#include 

// structure declarations
struct polar
{
    double distance;      // distance from origin
    double angle;         // direction from origin
};
struct rect
{
    double x;             // horizontal distance from origin
    double y;             // vertical distance from origin
};

// prototypes
polar rect_to_polar(rect xypos);
void show_polar(polar dapos);

int main()
{
    using namespace std;
    rect rplace;
    polar pplace;

    cout << "Enter the x and y values: ";
    while (cin >> rplace.x >> rplace.y)  // slick use of cin
    {
        pplace = rect_to_polar(rplace);
        show_polar(pplace);
        cout << "Next two numbers (q to quit): ";
    }
    cout << "Done.\n";

	system("pause");
	return 0;
}


// convert rectangular to polar coordinates
polar rect_to_polar(rect xypos)
{
    using namespace std;
    polar answer;

    answer.distance =
        sqrt( xypos.x * xypos.x + xypos.y * xypos.y);
    answer.angle = atan2(xypos.y, xypos.x);
    return answer;      // returns a polar structure
}

// show polar coordinates, converting angle to degrees
void show_polar (polar dapos)
{
    using namespace std;
    const double Rad_to_deg = 57.29577951;

    cout << "distance = " << dapos.distance;
    cout << ", angle = " << dapos.angle * Rad_to_deg;
    cout << " degrees\n";
}

《C++ primer plus》学习笔记——第七章C++函数的编程模块_第44张图片

说明:
(a)先来看看cin如何控制while循环的
《C++ primer plus》学习笔记——第七章C++函数的编程模块_第45张图片
(b)下面的cin的方式是:可以将某些数值排除在外,终止循环的方式。
如果程序在输入循环后还需要进行输入,则必须使用cin.clear()重置输入,然后还可能需要通过读取不合法的输入来丢弃它们。
《C++ primer plus》学习笔记——第七章C++函数的编程模块_第46张图片

3.传递结构的地址

(1)使用指向结构的指针,来传递结构的地址而不是整个结构可以节省时间和空间,因为值传递的话,使用的是结构的副本,而使用指针,能够对原始结构进行操作。

(2)对**2.另一个处理结构的函数示例(值传递)**中的函数重新编写
《C++ primer plus》学习笔记——第七章C++函数的编程模块_第47张图片

#include
#include

// strctptr.cpp -- functions with pointer to structure arguments
#include 
#include 

// structure templates
struct polar
{
    double distance;      // distance from origin
    double angle;         // direction from origin
};
struct rect
{
    double x;             // horizontal distance from origin
    double y;             // vertical distance from origin
};

// prototypes
void rect_to_polar(const rect * pxy, polar * pda);
void show_polar (const polar * pda);

int main()
{
    using namespace std;
    rect rplace;
    polar pplace;

    cout << "Enter the x and y values: ";
    while (cin >> rplace.x >> rplace.y)
    {
        rect_to_polar(&rplace, &pplace);    // pass addresses
        show_polar(&pplace);        // pass address
        cout << "Next two numbers (q to quit): ";
    }
    cout << "Done.\n";

	system("pause");
	return 0;
}


// show polar coordinates, converting angle to degrees
void show_polar (const polar * pda)
{
    using namespace std;
    const double Rad_to_deg = 57.29577951;

    cout << "distance = " << pda->distance;
    cout << ", angle = " << pda->angle * Rad_to_deg;
    cout << " degrees\n";
}

// convert rectangular to polar coordinates
void rect_to_polar(const rect * pxy, polar * pda)
{
    using namespace std;
    pda->distance =
        sqrt(pxy->x * pxy->x + pxy->y * pxy->y);
    pda->angle = atan2(pxy->y, pxy->x); 
}

《C++ primer plus》学习笔记——第七章C++函数的编程模块_第48张图片

说明:
(a)为了充分利用指针的效率,应该使用指针,而不是返回值
所以,需要将2个指针传递给函数,第一个指针指向要转换的结构void rect_to_polar(const rect * pxy, polar * pda),第二个指针指向要存储转换结果的结构void show_polar (const polar * pda)。
(b)函数不返回一个新的结构,而是修改调用函数中已有的结构。所以,用的都是void。。。还有在void rect_to_polar(const rect * pxy, polar * pda)中,第一个参数是const指针,但是第二个参数不是。

六、函数与string对象

(1)可将结构作为完整的实体传递给函数,也可以将对象作为完整的实体进行传递。
(2)如果需要多个字符串,可以声明一个string对象数组,而不是二维char数组。
(3)eg如下:
在这里插入图片描述

#include
#include

// topfive.cpp -- handling an array of string objects
#include 
#include 
using namespace std;
const int SIZE = 5;
void display(const string sa[], int n);
int main()
{
    string list[SIZE];     // an array holding 5 string object
    cout << "Enter your " << SIZE << " favorite astronomical sights:\n";
    for (int i = 0; i < SIZE; i++)
    {
        cout << i + 1 << ": ";
        getline(cin,list[i]);
    }

    cout << "Your list:\n";
    display(list, SIZE);
    // cin.get();

	system("pause");
	return 0;
}

void display(const string sa[], int n)
{
    for (int i = 0; i < n; i++)
        cout << i + 1 << ": " << sa[i] << endl;
}

说明:
(1)string数组
在这里插入图片描述

so,数组list的每个元素都是一个string对象
在这里插入图片描述

so,形参sa是一个指向string对象的指针,因此,sa[i]是一个string对象
在这里插入图片描述

七、函数与array对象(模板类array的数组)

(1)按照值将对象传递给函数,在这种情况下,函数处理的是原始对象的副本。
(2)也可以传递指向对象的指针,这样函数能够操作原始对象。
(3)看eg,array不仅能够存储基本数据类型,还可以存储类对象。
要点:
(a)要使用array类,需要包含头文件array,而名称array位于名称空间std中;
(b)显示expenses的内容,可以按照值传递;
在这里插入图片描述
(c)如果函数要修改对象expenses,可以将该对象的地址传递给函数
在这里插入图片描述
(d)程序的核心如下,
在这里插入图片描述
(e)
在这里插入图片描述

#include
#include

//arrobj.cpp -- functions with array objects
#include 
#include 
#include 
const int Seasons = 4;
const std::array Snames =
    {"Spring", "Summer", "Fall", "Winter"};

void fill(std::array * pa);
void show(std::array da);
int main()
{
    std::array expenses;
    fill(&expenses);
    show(expenses);
    // std::cin.get();
    // std::cin.get();

	system("pause");
	return 0;
}


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

void show(std::array da)
{
    double total = 0.0;
    std::cout << "\nEXPENSES\n";
    for (int i = 0; i < Seasons; i++)
    {
        std::cout << Snames[i] << ": $" << da[i] << '\n';
        total += da[i];
    }
    std::cout << "Total: $" << total << '\n';
}

《C++ primer plus》学习笔记——第七章C++函数的编程模块_第49张图片

说明:
(a)
在这里插入图片描述
说一下函数的缺点,
(b)
在这里插入图片描述
(c)
《C++ primer plus》学习笔记——第七章C++函数的编程模块_第50张图片
(d)使用引用开解决在效率和表示法上面的问题。

八、递归

递归在AI中是一种重要的工具

1.包含一个递归调用的递归

(1)如果递归函数调用自己,则被调用的函数也将调用自己,一直到终止调用链的内容。
(2)通常的方法是将递归调用放在if语句中
(3)eg,void类型的递归函数recurs()的代码如下:
《C++ primer plus》学习笔记——第七章C++函数的编程模块_第51张图片

具体eg如下:

#include
#include

// recur.cpp -- using recursion
#include 
void countdown(int n);

int main()
{
    countdown(4);           // call the recursive function
    // std::cin.get();

	system("pause");
	return 0;
}

void countdown(int n)
{
    using namespace std;
    cout << "Counting down ... " << n << endl;
    if (n > 0)
        countdown(n-1);     // function calls itself
    cout << n << ": Kaboom!\n";
}

《C++ primer plus》学习笔记——第七章C++函数的编程模块_第52张图片

说明:
(a)每个递归调用都创建自己的一套变量,因此当程序到达第5次调用时,将有5个独立的n变量,其中每个变量的值都不同!
《C++ primer plus》学习笔记——第七章C++函数的编程模块_第53张图片

2.包含多个递归调用的递归

(1)将一项工作不断分为两项较小的、类似的工作时,递归非常好用。
(2)递归方法有时被称之为分而治之策略(divide-and-conquer strategy
(3)看下面的eg,核心idea是:
在这里插入图片描述
对程序的解释如下:
在这里插入图片描述

#include
#include

// ruler.cpp -- using recursion to subdivide a ruler
#include 
const int Len = 66;
const int Divs = 6;
void subdivide(char ar[], int low, int high, int level);
int main()
{
    char ruler[Len];
    int i;
    for (i = 1; i < Len - 2; i++)
        ruler[i] = ' ';
    ruler[Len - 1] = '\0';
    int max = Len - 2;
    int min = 0;
    ruler[min] = ruler[max] = '|';
    std::cout << ruler << "\n"<

《C++ primer plus》学习笔记——第七章C++函数的编程模块_第54张图片

说明:
(a)尺子是一层一层往下走的,这样的思路去写的
《C++ primer plus》学习笔记——第七章C++函数的编程模块_第55张图片
(b)如果把下面红括号的注释掉,也可以得到同样的答案。
《C++ primer plus》学习笔记——第七章C++函数的编程模块_第56张图片

九、函数指针

1.函数指针的基础知识

(1)与数据项相似,函数也有地址。
函数的地址是存储其机器语言代码内存的开始地址
作用:可以编写将另一个函数的地址作为参数的函数,这样第一个函数将能够找到第二个函数,并运行它。它允许在不同的时间传递不同函数的地址,这意味着可以在不同的时间使用不同的函数
(2)将程序员要使用的算法函数的地址传递给另外一个函数,所需要完成的工作有:
(a)获取函数的地址
(b)声明一个函数指针
(c)使用函数指针来调用函数

具体来讲,如下所示:

(a)获取函数的地址具体如下:
获取函数的地址很简单,只要使用函数名即可(后面不跟参数)
如果think()是一个函数,则thin就是该函数的地址;
一定要区分传递的是函数的地址还是函数的返回值
eg:
在这里插入图片描述
process()调用使得process()函数能够在其内部调用think()函数
thought()调用首先调用think()函数,然后将think()的返回值传递给thought()函数。

(b)声明函数指针:比较棘手的是编写原型,而传递地址在非常简单
在这里插入图片描述
eg:假设一个函数原型如下所示:
在这里插入图片描述
则:正确的函数指针类型声明如下:
在这里插入图片描述

快速的函数指针写法如下:
要点(i)
在这里插入图片描述
要点(ii)
在这里插入图片描述

eg1如下:
《C++ primer plus》学习笔记——第七章C++函数的编程模块_第57张图片
eg2如下:
在这里插入图片描述
在这里插入图片描述

(c)使用指针来调用函数
(*pf)扮演的角色与函数名相同,因此使用(*pf)时,只需将它看作函数名即可。
《C++ primer plus》学习笔记——第七章C++函数的编程模块_第58张图片

2.函数指针的使用例子

(1)代码解释如下:
在这里插入图片描述

#include
#include

// fun_ptr.cpp -- pointers to functions
#include 
double betsy(int);
double pam(int);

// second argument is pointer to a type double function that
// takes a type int argument
void estimate(int lines, double (*pf)(int));

int main()
{
    using namespace std;
    int code;

    cout << "How many lines of code do you need? ";
    cin >> code;
    cout << "Here's Betsy's estimate:\n";
    estimate(code, betsy);
    cout << "Here's Pam's estimate:\n";
    estimate(code, pam);
    // cin.get();
    // cin.get();

	system("pause");
	return 0;
}
double betsy(int lns)
{
    return 0.05 * lns;
}

double pam(int lns)
{
    return 0.03 * lns + 0.0004 * lns * lns;
}

void estimate(int lines, double (*pf)(int))
{
    using namespace std;
    cout << lines << " lines will take ";
    cout << (*pf)(lines) << " hour(s)\n";
}

《C++ primer plus》学习笔记——第七章C++函数的编程模块_第59张图片

3.深入函数指针

(1)下面是一些函数的原型,他们的特征标和返回类型相同
在这里插入图片描述
说明:
(a)在函数原型中,参数列表const double ar []与const double * ar含义完全相同;
(b)在函数原型中,可以省略标识符
所以,const double ar []可以简化为const double [],
const double * ar 可以简化为const double * ;
(c)函数定义必须提供标识符,因此,需要使用const double ar [] 或const double * ar。

(2)假设要申明一个指针,它可以指向上面的三个函数。假定该指针名为pa,则只需要将目标函数原型中的函数名替换为(*pa)
《C++ primer plus》学习笔记——第七章C++函数的编程模块_第60张图片

在这里插入图片描述
说明:
cout的前半部分:(*pa)(av,3)和p2(av,3) 返回值类型为const double (即double值的地址),显示的都是一个double值的地址;
cout的后半部分
(pa)(av,3)和p2(av,3)都是查看存储在这些地址处的实际值。

(3)为了能够使用三个函数,可以使用函数指针数组
在这里插入图片描述
对于上述函数指针数组的定义的解释如下所示,
在这里插入图片描述

(4)因为自动类型推荐auto只能用于单值初始化,而不能用于初始化列表。
所以,要申明同样类型的数组就需要如下的操作:
在这里插入图片描述

数组名是指向第一个元素的指针,so,pa和pb都是指向函数指针的指针。pa[i]和pb[i]都表示数组中的指针,因此,可将任何一种函数调用表示法用于他们:
在这里插入图片描述

要获得指向double的值,可以使用运算符*:
在这里插入图片描述

(5)创建指向整个数组的指针
在这里插入图片描述

(6)如果直接创建一个指向整个数组的指针,该怎么办?
我们知道,
在这里插入图片描述
上面的,后面的表示的意思是:pd是一个指针,它指向一个包含三个元素的数组
由pa的声明,其结果如下所示:
在这里插入图片描述

对于上面的(4)和(6)的具体用法解释如下:
如果pd指向数组,那么*pd就是数组,(*pd)[i]就是数组中的元素,即函数指针
所以对于(4)而言,
在这里插入图片描述
对于(6)而言,
在这里插入图片描述

还有,对于(4)和(6)而言,pa与&pa之间的差别如下:
(a) pa是数组名,表示地址,在大多数情况下,pa都是数组第一个元素的地hi,即&pa[0];
(b) &pa是整个数组(即三个指针块)的地址。
(c)
在这里插入图片描述

#include
#include

// arfupt.cpp -- an array of function pointers
#include 
// various notations, same signatures
const double * f1(const double ar[], int n);
const double * f2(const double [], int);
const double * f3(const double *, int);

int main()
{
    using namespace std;
    double av[3] = {1112.3, 1542.6, 2227.9};

    // pointer to a function指向函数的指针
    const double *(*p1)(const double *, int) = f1;
    auto p2 = f2;  // C++0x automatic type deduction
    // pre-C++0x can use the following code instead
    // const double *(*p2)(const double *, int) = f2;
     cout << "Using pointers to functions:\n";
    cout << " Address  Value\n";
    cout <<  (*p1)(av,3) << ": " << *(*p1)(av,3) << endl;
    cout << p2(av,3) << ": " << *p2(av,3) << endl;//用了aoto后,这里是av[1]了,因为a2=f2

    // pa an array of pointers指针数组
    // auto doesn't work with list initialization
    const double *(*pa[3])(const double *, int) = {f1,f2,f3};
    // but it does work for initializing to a single value
    // pb a pointer to first element of pa
    auto pb = pa;
    // pre-C++0x can use the following code instead
    // const double *(**pb)(const double *, int) = pa;
    cout << "\nUsing an array of pointers to functions:\n";
    cout << " Address  Value\n";
    for (int i = 0; i < 3; i++)
        cout << pa[i](av,3) << ": " << *pa[i](av,3) << endl;
    cout << "\nUsing a pointer to a pointer to a function:\n";
    cout << " Address  Value\n";
    for (int i = 0; i < 3; i++)
        cout << pb[i](av,3) << ": " << *pb[i](av,3) << endl;

    // what about a pointer to an array of function pointers
	//用一个指针指向函数指针的数组
    cout << "\nUsing pointers to an array of pointers:\n";
    cout << " Address  Value\n";
    // easy way to declare pc 
    auto pc = &pa; 
     // pre-C++0x can use the following code instead
    // const double *(*(*pc)[3])(const double *, int) = &pa;
   cout << (*pc)[0](av,3) << ": " << *(*pc)[0](av,3) << endl;
    // hard way to declare pd
    const double *(*(*pd)[3])(const double *, int) = &pa;
    // store return value in pdb
    const double * pdb = (*pd)[1](av,3);
    cout << pdb << ": " << *pdb << endl;
    // alternative notation
    cout << (*(*pd)[2])(av,3) << ": " << *(*(*pd)[2])(av,3) << endl;
    // cin.get();

	system("pause");
	return 0;
}
// some rather dull functions

const double * f1(const double * ar, int n)
{
    return ar;
}
const double * f2(const double ar[], int n)
{
    return ar+1;
}
const double * f3(const double ar[], int n)
{
    return ar+2;
}

《C++ primer plus》学习笔记——第七章C++函数的编程模块_第61张图片

说明:
补充一下auto的知识:
《C++ primer plus》学习笔记——第七章C++函数的编程模块_第62张图片

4.使用typedef简化

《C++ primer plus》学习笔记——第七章C++函数的编程模块_第63张图片

十、大总结

《C++ primer plus》学习笔记——第七章C++函数的编程模块_第64张图片
《C++ primer plus》学习笔记——第七章C++函数的编程模块_第65张图片

《C++ primer plus》学习笔记——第七章C++函数的编程模块_第66张图片

你可能感兴趣的:(C++,CTL源码)