文章目录
0. 前言
《C++大学教程》 第6章 笔记更一下,附部分课后题代码。
6. 函数和递归入门
6.3 数学库函数
不是类的成员函数的函数称为全局函数
<cmath>
头文件向用户提供了能够执行常见的数学计算的一个函数集合,其中的所有函数都是全局函数
6.4 具有多个形参的函数定义
如果用户对函数实参的求值顺序,以及是否由此影响传递给函数的值有所疑惑,那么应在调用函数之前使用单独的赋值语句对实参进行求值,把每个表达式的结果赋给局部变量,然后再将这些变量作为实参传递给函数
形参表中的每一个形参都需要一个显式的类型说明
6.5 函数原型和实参类型的强制转换
通过使用#include
预处理指令来包含相应的C++标准库头文件,可以得到库函数的函数原型
提供函数原型避免了按照函数定义的顺序来使用函数的编码约束
函数原型的函数名和实参类型部分称为函数签名
6.7 实例研究:随机数生成
重复地调用rand
函数产生一个伪随机数序列。但是,程序每次执行都会产生相同的序列。
srand
函数接收一个unsigned
整型形参,为rand
函数设置产生随机数时的随机数种子,从而使rand
函数在每次执行程序时都生成不同的随机数序列
使用当前时间向随机数生成器提供种子:
srand(static_cast<unsigned int>(time(0)));
time
函数(实参为0)通常返回的是格林尼治标准时间(GMT)1970年1月1日0时起到现在的秒数。这个值(数据类型是time_t
)被转换成unsigned int
类型的整数,并用做随机数生成器的种子
time
函数的函数原型在<ctime>
头文件中
6.8 实例研究:博彩游戏和枚举类型简介
枚举类型以关键字enum
开头,后跟类型的名字和一组由标识符表示的整数常量
枚举类型的默认起始值是0并且顺序增1,当然可以自行指定起始值。
在一个枚举中的标识符必须是唯一的,但是不同的枚举常量可以取相同的整数值
在枚举定义中,任何枚举常量都可以被赋予一个整数值,其后的每个枚举常量的值是列表中前一个枚举常量的值加上1,直到下一个显式的设置为止
作为用户自定义类型名的标识符,其第一个字母最好是大写
枚举常量的名字只使用大写字母,突出显示,并提醒程序员这些枚举常量不是变量
在默认情况下,无作用域限定的枚举类型所隐含的整数类型取决于它的枚举常量值,也就是说该整数类型应保证足以保存指定的常量值。
不过在默认情况下,作用域限定的枚举类型所隐含的整数类型是int
。
C++ 11 允许程序员指定枚举类型所隐含的整型类型,方式是在枚举类型名称后跟随一个冒号(:)和指定的整型类型
enum class Status : unsigned int { CONTINUE, WON, LOST };
6.9 C++ 11 的随机数
#include <iostream>
#include <iomanip>
#include <random>
#include <ctime>
using namespace std;
int main()
{
default_random_engine engine( static_cast<unsigned int>( time(0) ) );
uniform_int_distribution<unsigned int> randomInt( 1, 6 );
for ( unsigned int counter = 1; counter <= 100; ++counter )
{
cout << setw( 10 ) << randomInt( engine );
if ( counter % 5 == 0 )
cout << endl;
}
}
类型default_random_engine
表示默认的随机数生成引擎,它的构造函数的实参利用当前的时间来设置随机数生成器引擎的种子。
uniform_int_distribution
在指定的值的范围内均匀地分布伪随机整数,默认的范围是从0到计算机系统平台所支持的最大的int
类型值。
6.10 存储类别和存储期
标识符的存储期决定了标识符在内存中存在的时间
- 具有自动存储期的变量包括:
声明在函数中的局部变量、函数的形参和用register声明的局部变量或函数形参
在程序执行到定义它们的语句块时被创建,而当程序退出语句块时它们被销毁 - 关键字
extern
和static
为函数和具有静态存储期的变量声明标识符。
具有静态存储期的变量从程序开始执行的时刻起直至程序执行结束,一直存在于内存中。
在遇到这样的变量声明时,便对它进行一次性初始化。
对于函数而言,在程序开始执行时函数名存在。
全局变量是通过把变量声明放在任何类和函数定义外部来创建的。全局变量在整个程序执行过程中保存它们的值。全局变量和全局函数可以被源文件中位于其声明或者定义之后的任何函数引用。
使用关键字static
声明的局部变量仅被其声明所在的函数所知。但是,与自动变量不同的是,static
局部变量在函数返回到它的调用者后仍保留着它们的值。下次再调用函数时,static
局部变量包含的是该函数最后一次执行得到的值。
6.11 作用域规则
标识符的作用域是指标识符在程序中可以被引用的范围
- 语句块作用域:开始于标识符的声明处,结束于标识符声明所在语句块的结束右括号处。
- 函数作用域:标签是唯一具有函数作用域的标识符。标签可以用在它们出现的函数内的任何地方,但是不能在函数体之外被引用。
- 全局命名空间作用域:声明于任何函数或者类之外的标识符具有全局命名空间作用域。这种标识符从其声明处开始直到文件结尾处为止出现的所有函数而言都是可访问的。
- 函数原型作用域:用在函数原型形参列表中的标识符。
#include <iostream>
using namespace std;
void useGlobal();
int x = 1;
int main()
{
... ...
useGlobal();
... ...
}
void useGlobal()
{
cout << "\nglobal x is " << x << " on entering useGlobal" << endl;
x *= 10;
cout << "global x is " << x << " on exiting useGlobal" << endl;
}
useGlobal
函数没有声明任何变量。因此,当引用变量x
时,使用的是全局变量x
。
6.15 引用和引用形参
按值传递:
当实参用按值传递的方式传递时,会(在函数调用堆栈上)产生一份实参值的副本,然后将副本传递给被调用的函数。
对于副本的修改不会影响调用函数中原始变量的值。
按值传递的一个缺点:如果有一个大的数据项需要传递,那么复制这些数据就需要花费大量的时间和内存空间。
引用形参:在函数原型中形参类型后加一个&
标识
利用按引用传递,调用者使被调用函数可以直接访问调用者的数据,并且可以修改这些数据。
6.16 默认实参
默认实参必须是函数形参列表中最靠右边(尾部)的实参。当调用具有两个或者更多个默认实参的函数时,如果省略的不是实参列表中最靠右边的实参,那么该实参右边的所有实参也必须省略掉。
显式地传递给函数的任何实参都按从左到右的顺序赋值给了函数的形参。
6.17 一元的作用域分辨运算符
#include <iostream>
using namespace std;
int number = 7;
int main()
{
double number = 10.5;
cout << "Local double value of number = " << number
<< "\nGlobal int value of number = " << ::number << endl;
}
局部变量和全局变量是有可能被声明为相同的名字的。C++提供了一元的作用域分辨运算符(::),在同名的局部变量的作用域内,可以用来访问全局变量。
6.18 函数重载
C++允许定义多个具有相同名字的函数,只要这些函数具有不同的函数签名。这种特性称为函数重载。
当调用一个重载函数时,C++编译器通过检查函数调用中的实参数目、类型和顺序来选择恰当的函数。
函数重载通常用于创建执行相似任务、但是作用于不同的数据类型的具有相同名字的多个函数(与函数的返回类型无关)。
创建具有相同形参列表和不同返回类型的重载函数会产生编译错误。
6.19 函数模板
如果对于每种数据类型程序逻辑和操作都是相同的,那么使用函数模板可以使重载执行起来更加紧凑和方便。
template < typename T >
T maximum( T value1, T value2, T value3 )
{
T maximumValue = value1;
if ( value2 > maximumValue )
maximumValue = value2;
if ( value3 > maximumValue )
maximumValue = value3;
return maximumValue;
}
该程序定义了maximum
函数模板,用于确定三个数值中的最大值。
所有的函数模板都以template
关键字开头,后面跟着用一对尖括号括起的该函数模板的模板形参列表。
模板形参列表中的每个形参(常常称作形式类型形参)由关键字typename
或者关键字class
开头。
形式类型形参是基本类型或者用户自定义类型的占位符。占位符(该程序占位符为T
)用于指定函数形参的类型,指定函数的返回类型,以及在函数定义体内声明变量。
6.20 递归
递归步骤通常包括关键字return
。
6.21 递归应用示例:Fibonacci(斐波那契)数列
C++语言只指定了4种运算符操作数的求值顺序:&&
、||
、,
和?:
前三个是二元运算符,它们的两个操作数是按从左到右的顺序进行求值的。
最后一个运算符是C++中唯一的三元运算符,其最左边的操作数总是先被求值。如果最左边的操作数的求值结果为真,就接着求中间的操作数的值,最后的操作数被忽略;如果最左边的操作数的求值结果为假,接着就求第三个操作数的值,中间的操作数被忽略。
如果程序依赖于不包括&&
、||
、,
和?:
在内的其他运算符的操作数的求值顺序,那么它们在使用不同的编译器时会有不同的表现,因此可能导致逻辑错误。
&&
和||
采用的是短路求值
6.22 递归与迭代
递归使用选择结构,通过重复的函数调用实现循环。
递归产生比原来的问题更简单的问题直到达到基本情况。
任何可以用递归解决的问题都可以用迭代(非递归)解决。如果使用递归方法能够更自然地反映问题,并且能够使程序更易于理解和调试。
在要求性能的情况下应避免使用递归。递归调用会消耗额外的时间和内存。
练习题
6.36 递归的求幂计算
测试程序:
#include <iostream>
#include <cmath>
using namespace std;
double power(double, int);
int main()
{
int exponet = 0;
double base = 0;
cout << "Input base(double) and exponent(positive integer): ";
cin >> base >> exponet;
cout << "The answer: " << power(base, exponet) << endl;
system("pause");
}
double power(double base, int exponet)
{
if (exponet == 1)
return base;
else
return base * power(base, exponet - 1);
}
6.37 斐波那契数列的迭代版本
见参考链接1.
测试程序:
#include <iostream>
using namespace std;
int FibonacciD(int num);
int main()
{
int i;
cout << "Input the number: " << endl;
cin >> i;
cout << "\nFibonacci(" << i << ") = \n" << FibonacciD(i) << endl;
system("pause");
}
int FibonacciD(int num)
{
if (num <= 0) {
return 0;
}
if (num == 1 || num == 2)
{
return 1;
}
int first = 1, second = 1, third = 0;
for (int i = 3; i <= num; i++)
{
third = first + second;
first = second;
second = third;
}
return third;
}
6.52 函数模板 minimum
头文件:
template<typename S>
S minimum(S num1, S num2, S num3)
{
S minimumNum = num1;
if (num2 < minimumNum)
minimumNum = num2;
if (num3 < minimumNum)
minimumNum = num3;
return minimumNum;
}
测试程序:
#include <iostream>
#include "minimum.h"
using namespace std;
int main()
{
int int1, int2, int3;
cout << "Input three integer values: " << endl;
cin >> int1 >> int2 >> int3;
cout << "The three integer values are: " << int1 << ' '<< int2 << ' ' << int3 << endl;
cout << "The minimum integer value is: " << minimum(int1, int2, int3) <<endl;
double d1, d2, d3;
cout << "\nInput three double values: " << endl;
cin >> d1 >> d2 >> d3;
cout << "The three double values are: " << d1 << ' ' << d2 << ' ' << d3 << endl;
cout << "The minimum double value is: " << minimum(d1, d2, d3) << endl;
char c1, c2, c3;
cout << "\nInput three characters: " << endl;
cin >> c1 >> c2 >> c3;
cout << "The three characters are: " << c1 << ' ' << c2 << ' ' << c3 << endl;
cout << "The minimum character value is: " << minimum(c1, c2, c3) << endl;
system("pause");
}
6.55 C++ 11 的随机数:掷双骰子游戏的改进
测试程序:
#include <iostream>
#include <random>
#include <ctime>
using namespace std;
unsigned int rollDice();
int main()
{
enum Status { CONTINUE, WON, LOST };
unsigned int myPoint = 0;
Status gameStatus = CONTINUE;
unsigned int sumOfDice = rollDice();
switch (sumOfDice)
{
case 7:
case 11:
gameStatus = WON;
break;
case 2:
case 3:
case 12:
gameStatus = LOST;
break;
default:
gameStatus = CONTINUE;
myPoint = sumOfDice;
cout << "Point is " << myPoint << endl;
break;
}
while (CONTINUE == gameStatus)
{
sumOfDice = rollDice();
if (sumOfDice == myPoint)
gameStatus = WON;
else
if (sumOfDice == 7)
gameStatus = LOST;
}
if (WON == gameStatus)
cout << "Player wins" << endl;
else
cout << "Player loses" << endl;
system("pause");
}
unsigned int rollDice()
{
default_random_engine engine(static_cast<unsigned int>(time(0)));
uniform_int_distribution <unsigned int> randomInt(1, 6);
unsigned int die1 = randomInt(engine);
unsigned int die2 = randomInt(engine);
unsigned int sum = die1 + die2;
cout << "Player rolled " << die1 << " + " << die2
<< " = " << sum << endl;
return sum;
}
结语
本来想六七章一起更的,第六章写到后边,觉得篇幅已经够了,这次就只更一个单元。
考试越来越近,下一篇得先搁置了。
想做一个没有感情的,学习机器。远离各种纷纷扰扰,岂不很爽?
个人水平有限,有问题欢迎各位大神批评指正!
参考博客
- 斐波那契数列的迭代算法和递归算法
https://blog.youkuaiyun.com/sun_jinhang/article/details/88395958