首页 > 其他分享 >05.函数和递归

05.函数和递归

时间:2024-08-31 11:49:15浏览次数:5  
标签:函数 递归 05 int 类型 实参 模板

5. 函数和递归

inline functions ---内联函数 function template---函数模板

5.1 C++中的程序构件

function prototype---函数原型

5.1.1 函数原型

A function prototype is a declaration of a function that tells the compiler the function’s name, its return type and the types of its parameters.

参数强制转换是编译器可以将参数从一种类型隐式转换为另一种类型的一种技术。 它遵循论据提升规则。 如果一个参数是较低的数据类型,则可以将其转换为较高的数据类型,但反之则不成立。 原因是如果将一个较高的数据类型转换为较低的数据类型,则可能会丢失一些数据。

5.2 C++库头文件


macro---宏

5.3 随机数的产生

随机数的产生可以使用i=rand();

rand函数产生一个0到RAND_MAX之间的无符号整数,如果rand真的产生了一个任意整数,那么在范围内的整数的出现概率是相同的。头文件为 <cstdlib>

为了产生范围在0-5之内的随机整数,我们使用:

rand()%6;

由于要模拟骰子的点数,所以在后面+1.

C++14引入了一个新的语法特性,即数字分隔符(Digit Separators,也被称为"单下划线")。这个特性的引入,主要是为了提高代码的可读性。在处理大量数字,特别是长数字串时,数字分隔符可以帮助我们更清晰地看到数字的大小和单位。例如,我们可以将一个长整数1000000000写成1'000'000'000

rand实际上产生的是伪随机数(pseudorandom numbers),Repeatedly calling rand produces a sequence of numbers that appears to be random. However, the sequence repeats itself each time the program executes.

srand函数的头文件为:

需要一个无符号整数实参以及seeds rand函数来创造一组随机数,且每次执行产生的一组随机数都是不同的。

rand()函数和srand()函数必须配套使用

#include <iostream>
#include <iomanip>
#include <cstdlib>
using namespace std;

int main() {
   unsigned int seed{0};
   cout<<"enter seed: ";
   cin>>seed;
   srand(seed);

   for(unsigned int counter{1};counter<=10;++counter){
       cout<<setw(10)<<(1+rand()%6);
       if(counter%5==0){
           cout<<endl;
       }
   }
}

还可以使用时间作为种子(seed),为了不必每次都要输入seed,我们可以使用如下代码:

srand(static_cast<unsigned int>(time(0)));

time函数返回自从1970年1月1号到当前时间的秒数,返回的值类型为time_t,头文件为,由于srand需要无符号整数作为实参,所以采用static_cast将之转化为合适的类型。

5.3.1 放缩及平移随机数

where the shiftingValue is equal to the first number in the desired range of consecutive integers and the scalingFactor is equal to the width of the desired range of consecutive integers

5.4 枚举类型(enum)

关键字enum class,后面为类型名和一组表示整数常量的标识符。例如下述代码:

enum class Status{CONTINUE,WON,LOST};

CONTINUE代表0, WON代表1, LOST代表2

一个枚举类中的标识符必须是唯一的,但是不同的枚举常数可以有相同的整数值。用户自定义类型Status的变量只能赋值在枚举中声明的三个值中的一个。

按照惯例,你应该把一个enum class 名字的第一个字母大写。

另一个流行的scoped enumeration是:

enum class Months {JAN = 1, FEB, MAR, APR, MAY, JUN, JUL, AUG, SEP, OCT, NOV, DEC};

第一个值被设置为1,剩下的值就会从1开始递增,任何一个枚举常数都可以在枚举定义中被赋予一个整数值,并且后续的枚举常数每个都比列表中的前一个常数高一个值1,直到下一个显式设置。

标识符表示的默认为整数(int),但是也可以指定不同的类型。

enum class Status : unsigned int {CONTINUE, WON, LOST};

5.5 C++11中的随机数

使用rand函数,不具备很好的统计特性,仍然可以被预测到,在C++11中,引入了头文件<random>

在本节中,我们将使用默认的随机数生成引擎- -default_random_engine和均匀分布的uniform_int_distribution,将伪随机整数均匀分布在指定的取值范围内。默认范围是从0到你的平台上的一个int的最大值。

class template---类模板

#include <iostream>
#include <iomanip>
#include <random>
#include <ctime>
using namespace std;

int main() {
    default_random_engine engine{static_cast<unsigned int>(time(nullptr))};
    uniform_int_distribution<unsigned int> randomInt{1, 6};

    for(unsigned int counter{1};counter<=10;++counter){
        cout<<setw(10)<<randomInt(engine);

        if(counter%5==0){
            cout<<endl;
        }
    }
}

5.6 作用域规则

当块是嵌套的,并且外块中的标识符与内块中的标识符具有相同的名字时,外块中的标识符被"隐藏",直到内块终止- -内块"看到"自己的局部变量的值而不是封闭块的相同命名变量的值。

局部变量也可以被声明为静态的。这样的变量也有块范围,但与其他局部变量不同的是,一个静态的局部变量在函数返回到它的调用者时保留了它的值。下次调用该函数时,静态局部变量包含该函数上次执行完毕时的值

所有数值类型的静态局部变量默认初始化为零。下面的语句声明静态局部变量计数,并将其初始化为0:

static unsigned int count;

An identifier declared outside any function or class has global namespace scope.

全局变量是通过在任何类或函数定义之外放置变量声明来创建的。这些变量在程序执行的整个过程中保持其值。

将变量声明为全局而非局部,当一个不需要访问变量的函数意外或恶意修改变量时,就会出现意想不到的副作用。这是最小特权原则的另一个例子- -除了真正的全局资源,如cin和cout,全局变量应该避免。一般来说,变量应该在其需要访问的最窄范围内进行声明。仅在特定函数中使用的变量应声明为该函数中的局部变量而不是全局变量。

函数原型中的参数,其作用域始于左括号,终于右括号

double area(double radius);//radius的作用域仅在于括号内,不能用于程序正文以及其他地方

类的成员具有类作用域,其范围包括类体和非内联成员函数的函数体。

如果在类作用域以外访问类的成员,要通过类名(访问静态成员),或者该类的对象名、对象引用、对象指针(访问非静态成员)。

5.7 函数调用栈和激活记录

5.7.1 栈

pushing---进栈 popping---出栈

栈是后人先出结构(last-in, first-out (LIFO) data structures),即最后一个进栈的会最先出栈。

函数调用栈(function-call stack),有时也称为程序执行栈,和数据结构意义上的“栈”不同。

栈帧(Stack Frames)

每次函数调用其他函数,一条记录就会放入栈中,这条记录,就叫做栈帧或者活动记录(activation record),包含被调用函数为了返回调用函数所需要的返回地址。如果被调用的函数返回而不是在返回之前调用另一个函数,则弹出函数调用的堆栈帧,控制转移到弹出堆栈帧中的返回地址。If the called function returns instead of calling another function before returning, the stack frame for the function call is popped, and control transfers to the return address in the popped stack frame.

调用栈的美妙之处在于,每个被调用的函数总是在调用栈的顶端找到它需要返回给它的调用者的信息。并且,如果一个函数对另一个函数进行了调用,新函数调用的栈帧就被简单地推到了调用栈上。这样,新被调用函数返回其调用者所需的返回地址就位于栈的顶端。

函数在执行过程中需要存在非静态的局部变量。如果函数调用其他函数,它们需要保持活动状态。但是当一个被调用函数返回到它的调用者时,被调用函数的非静态局部变量需要"离开"。被调用函数的堆栈框架是为被调用函数的非静态局部变量预留内存的绝佳场所。只要被调用的函数是活动的,该栈帧就存在。当该函数返回而不再需要它的非静态局部变量时,它的堆栈框架从堆栈中弹出,并且这些非静态局部变量不再存在。

stack overflow(栈溢出)

#include <iostream>
using namespace std;

int square(int);

int main(){
    int a{10};
    cout << a << " squared: " <<square(a) << endl;
}

int square(int x){
    return x*x;
}

5.8 内联函数(inline functions)

从软件工程的观点来看,将程序作为一组函数来实现是很好的,但是函数调用涉及执行时间开销。C++提供内联函数来帮助减少函数调用开销。

将限定符置于函数定义中函数的返回类型之前,建议编译器在函数调用的每个地方(适当的时候)生成函数体代码的副本,以避免函数调用。这往往会使程序变大。编译器可以忽略内联限定符,一般对除最小函数外的所有函数都这样做。可重复使用的内联函数通常放置在头文件中,因此它们的定义可以包含在每个使用它们的源文件中。

如果一个函数是内联的,那么在编译时,编译器会把该函数的代码副本放置在每个调用该函数的地方。对内联函数进行任何修改,都需要重新编译函数的所有客户端,因为编译器需要重新更换一次所有的代码,否则将会继续使用旧的函数。如果想把一个函数定义为内联函数,则需要在函数名前面放置关键字 inline,在调用函数之前需要对函数进行定义。

内联函数的声明和定义一般不分开(也可以分开,具体参见如下链接)c++ - Is possible to separate declaration and definition of inline functions? - Stack Overflow

对内联函数不能进行异常接口说明

内联函数的定义必须出现在内联函数第一次被调用之前。

如果已定义的函数多于一行,编译器会忽略 inline 限定符

inline只适合函数体内代码简单的函数使用,不能包含复杂的结构控制语句例如while、switch,并且内联函数本身不能是直接递归函数(自己内部还调用自己的函数)。

以下情况不宜使用内联:

(1)如果函数体内的代码比较长,使用内联将导致内存消耗代价较高。

(2)如果函数体内出现循环,那么执行函数体内代码的时间要比函数调用的开销大。

(3)内联函数不能递归。

#include <iostream>
using namespace std;

inline double cube(const double side){
   return side*side*side;
}

int main(){
    double sideValue;
    cout<<"Enter the side length of your cube: ";
    cin >> sideValue;
    
    cout<<"Volume of cube with side "
    <<sideValue<<" is "<<cube(sideValue)<<endl;
}

当函数在类的声明中实现,它自动成为内联函数。

class A{
public:
    A()=default;
    double f1(){
        //do something
    }
    double f2();
};

double A::f2(){
    //do something
}
//f1就变成了内联函数
//f2不是内联函数

5.9 函数默认参数

某些函数有这样一种形参, 在函数的很多次调用中它们都被赋予一个相同的值, 我们把这个反复出现的值称为函数的默认实参。

当程序在函数调用时,省略形参的默认实参,编译器会重写函数调用并向那个实参插入默认值

#include <iostream>
using namespace std;

unsigned int boxVolume(unsigned int length=1,unsigned int width=1,unsigned int height=1);

int main() {
    cout<<"The default box volume is: "<<boxVolume();

    cout<<"\n\nThe volume of a box with length 10,\n"
    <<"width 1 and height 1 is: "<<boxVolume(10);

    cout<<"\n\nThe volume of a box with length 10,\n"
    <<"width 5 and height 2 is: "<<boxVolume(10,5,2)<<endl;
}

unsigned int boxVolume(unsigned int length,unsigned int width,unsigned int height){
    return length*width*height;
}

在上述代码中,函数的原型指明三个形参都有默认值1。第一次调用boxVolum函数,没有指定实参,所以使用的是三个默认值1。第二次调用只传递了一个length实参,所以width和height仍然使用的默认实参1。最后一次调用,传入了三个实参,就不要默认实参了。

任何显式传递给函数的实参均从左至右赋值给函数的形参

默认实参必须是函数形参列表最右边的实参。当调用的函数有两个或者多个默认实参,如果一个遗漏的实参不是最右边的实参,那么所有的实参都必须被省略。默认实参必须在函数名称首次出现时指明---典型的,在函数原型中。如果函数原型遗漏了,那么默认实参应该在函数头中指明。默认值可以是任意表达式,包括常量,全局变量或者函数调用。默认实参也可以在内联函数中使用。

定义时应当注意:参数列表中默认值参数应后置
void t1(int x,int y=0,int z);//错误
void t2(int x-0,inty=0,int z);//错误

void t3(int x,int y=0,int z=0);//正确
void t4(int x=0,int y=0;int z=0);//正确

调用时应当注意:参数列表中实参应前置
t3(1, ,7);//错误
t4( , ,6);//错误

t3(1);//正确,y,z使用默认值
t4(1,2);//正确,z使用默认值

函数重载时,不允许重定义默认参数。

#include <iostream>
using namespace std;

void printArea(double radius=1.0){
    cout<<radius*2<<endl;
}

void printArea(int radius=2){
    cout<<radius*radius<<endl;
}

int main() {

    printArea();
    printArea(4);

    return 0;
}

5.10 一元作用域解析运算符(:

标签:函数,递归,05,int,类型,实参,模板
From: https://www.cnblogs.com/yyyylllll/p/18390057

相关文章

  • io进程,标准io(函数接口)
    3.函数接口3.1打开文件fopenFILE*fopen(constchar*path,constchar*mode);功能:打开文件参数:path:打开的文件路径mode:打开的方式r:只读,当文件不存在时报错,文件流定位到文件开头r+:可读可写,当文件不存在时报错,文件流定位到文件开头w:只写,文件不存在创建,存在则清空w......
  • 信息学奥赛一本通1058:求一元二次方程
    【题目描述】利用公式x1=−b+b2−4ac√2a,x2=−b−b2−4ac√2a�1=−�+�2−4��2�,�2=−�−�2−4��2�,求一元二次方程ax2+bx+c=0��2+��+�=0的根,其中a�不等于00。结果要求精确到小数点后55位。【输入】输入一行,包含三个浮点数a,b,c�,�,�(它们之间以一个空格分开),分别表示方程ax2+bx+c=0��2+��+�=0的系数。......
  • 信息学奥赛一本通1056:点和正方形的关系
    【题目描述】有一个正方形,四个角的坐标(x,y)分别是(1,-1),(1,1),(-1,-1),(-1,1),x是横轴,y是纵轴。写一个程序,判断一个给定的点是否在这个正方形内(包括正方形边界)。如果点在正方形内,则输出yes,否则输出no。【输入】输入一行,包括两个整数x、y,以一个空格分开,表示坐标(x,y)。【输出......
  • 信息学奥赛一本通1055:判断闰年
    【题目描述】#include<iostream>usingnamespacestd;intmain(){ inta; cin>>a; if(a%4==0&&a%100!=0||a%400==0){ cout<<"Y"; } else{ cout<<"N"; } return0;}判断某年是否是闰年。如果公元a年是闰年输出Y,否则输出N......
  • 信息学奥赛一本通1054:三角形判断
    【题目描述】给定三个正整数,分别表示三条线段的长度,判断这三条线段能否构成一个三角形。如果能构成三角形,则输出“yes”,否则输出“no”。【输入】输入共一行,包含三个正整数,分别表示三条线段的长度,数与数之间以一个空格分开。【输出】如果能构成三角形,则输出“yes”,......
  • Python基础 3 - 函数及数据容器
    文章目录一、函数概念1、函数介绍2、函数的定义3、函数的调用4、函数说明文档5、函数嵌套调用6、变量作用域1)局部变量2)全局变量3)声明全局变量二、数据容器入门1、列表(list)1)列表的定义2)调用列表元素3)列表的方法4)列表的特点5)列表的遍历(迭代)6)列表的乘......
  • C语言文件相关函数
    目录一、引言二、文件函数概述  1.文件类型指针  2.常用文件函数三、文件函数使用详解  1.打开文件  2.关闭文件  3.写入文件  4.读取文件  5.二进制文件操作四、实战应用五、总结        本文将详细介绍C语言中文件函数的基本概念......
  • 线程(函数接口、同步、互斥、条件变量)
    线程Thread1.什么是线程1.1概念线程是一个轻量级的进程,为了提高系统的性能引入线程。线程和进程是参与统一的调度。在同一个进程中可以创建的多个线程,共享进程资源。(Linux里同样用task_struct来描述一个线程)1.2进程和线程区别相同点:都为系统提供了并发执行的......
  • 信奥一本通题南沙陈老师解题 1058:求一元二次方程
     【题目描述】【输入】输入一行,包含三个浮点数a,b,ca,b,c(它们之间以一个空格分开),分别表示方程ax2+bx+c=0ax2+bx+c=0的系数。【输出】输出一行,表示方程的解。若两个实根相等,则输出形式为:“x1=x2=...x1=x2=...”;若两个实根不等,在满足根小者在前的原则,则输出形式......
  • JS逆向入门案例-xx志愿服务网encData-05
    文章目录概要整体架构流程技术细节小结概要提示:仅供学习,不得用做商业交易,如有侵权请及时联系!逆向:xx志愿服务网URL:aHR0cDovL2NoaW5hdm9sdW50ZWVyLm1jYS5nb3YuY24vc2l0ZS9wcm9qZWN0目标:表单数据中的encData参数整体架构流程提示:分析-调试-猜想-实现-执......