第六章 函数

函数

函数基础

函数包括返回类型、函数名字、0个或者多个形参组成的列表、函数体

int fact(int val) //val是形参
{
    ...
    return (int); 
}
int main()
{   
   int ans = fact(5); //括号是调用运算符、5是实参
   return 0;
}

函数调用完成两项工作:用实参初始化对应的形参,将控制权转移给被调用函数,此时主调函数的执行被暂时中断。
形参一定会被初始化。实参是形参的初始值。

局部对象

名字有作用域,对象有生命周期

  • 对象的生命周期是程序执行过程中该对象存在的一段时间。
  • 局部变量:形参和函数体内定义的变量统称为局部变量。局部变量仅在函数的作用域内可见,同时局部变量还会隐藏在外层作用域中的同名的其他所有声明中。
  • 自动对象:当函数的控制路径经过变量定义语句时创建该对象,当到达定义所在的块末尾是销毁它。我们把只存在于块执行期间的对象称为自动对象。形参是一种自动对象
  • 局部静态变量:局部静态对象在程序的执行路径第一次经过对象定义语句时初始化,并且直到程序终止才被销毁。可以将局部变量定义成static类型从而获得这样的对象。

函数声明

函数声明也称作函数原型。
建议变量在头文件中声明, 在源文件中定义,函数也应该在头文件中声明,在源文件中定义。

//Chapter6.h
#pragma once
#ifndef CHAPTER6_H
#define CHAPTER6_H
#include <cmath>
int fact(int);
int fact(int add1, int add2);
#endif
//Chapter6.cpp
#include "Chapter6.h"
int fact(int num)
{
    return abs(num);
}
int fact(int add1, int add2)
{
    return add1 + add2;
}

参数传递

  • 如果形参是引用类型,它将绑定到对应的实参上;否则,将实参的值拷贝后赋值给形参。
  • 指针形参 :建议使用引用类型代替指针。

传引用参数

拷贝大的类类型对象或者容器对象比较低效,甚至有的类类型不支持拷贝操作,当某种类型不支持拷贝操作时,函数只能通过引用形参访问该类型的对象。
当函数无需修改引用形参的值时最好使用常量引用。

const形参和实参

当形参有顶层const时, 传给它常量对象或者非常量对象都是可以的。反之,不行。不能将常量对象传给非常量形参。

数组形参

数组的性质:不允许拷贝数组,使用数组是通常会将其转换成指针。

  • 不能以值传递的方式传递数组,但是我们可以把形参写成类似数组的形式。
void print(const int*);
void print(const int[]);
void print(const int[10]); //这里的维度表示我们期望含有多少元素,实际不一定。
//以上三个函数的唯一形参都是const int*.

int i = 0, j[2] = {0, 1};
print(&i); //&i 的类型是int *
print(j); //j 转换成int* 并指向j[0];
  • 管理指针形参的三种常用技术
  1. 使用标记指定数组长度(c风格字符串)
  2. 使用标准库规范(传入首元素和尾后元素的指针)
  3. 显式传递一个表示数组大小的形参(定义一个表示数组大小的形参)
  • 当函数确实要改变元素值时, 才把形参定义成非常量的指针。
  • 数组引用形参
int &arr[10]; //错误 : 将arr声明成了引用的数组(有十个引用的数组)。
int (&arr)[10]; //arr是含有十个整数的整型数组的引用。

数组的大小是构成数组类型的一部分,所以只要不超过维度,在函数体内就可以放心的使用数组。但是同样限制了print函数的可用性, 只能将函数作用于大小为10的数组。

有返回值的函数

return:不要返回局部对象或指针

const string &mainp()
{
    string ret = "abc";
    return ret;//错误, return返回的是局部对象的引用
}
const string &mainp()
{
    return "HHH"; //错误,字符串字面值转换成一个局部临时string对象,同ret一样都是局部的。
}

当函数结束时临时对象所占的空间也就随之释放掉了,所以两条return都指向了不再可用的内存空间。

返回数组指针

数组不能拷贝,所以函数不能返回数组,但是可以返回数组的指针或者引用.

typedef int arrT[10];  //arrT 是类型别名, 表示的类型是含有10个整数的数组
using arrT = int [10]; //arrT 的等价声明
arrT* func(int i);     //func 返回一个指向含有10个整数的数组的指针

如果不使用类型别名, 必须记住,数组的维度要跟随要定义的数组名之后。

int arr[10];   //arr 是一个含有10个整数的数组
int *p1[10];   //p1 是一个含有10个指针的数组
int (*p2)[10] = &arr; // p2 是一个指针, 指向含有10个整数的数组

定义返回数组指针的函数形式: Type (*function (parameter_list))[dimersion]
Type 是元素类型, dimension是数组大小,(*function (parameter_list)) 调用这个函数后会得到一个大小是dimersion的数组。

函数重载

  1. 如果同一作用域内的几个函数名字相同但形参列表不同,我们称之为重载函数。
  2. 当调用名字相同的函数时,会根据传递的实参类型推断想要的是哪个函数。不允许两个函数除了返回类型外其他所有的要素都相同
Record lookup(const Account&);
bool lookup(const Account&); /错误,只有返回类型不同
  • 判断两个形参类型是否相异
Record lookup(const Account &acct); // 给形参起了名字
Record lookup(const Account&); //相同, 省略了形参的名字

Phone phone;
typedef Phone telno;
Record lookup(const Phone&); 
Record lookup(const telno);  //telno 和 Phone 的类型相同
  • 重载和 const 形参
Record lookup (Phone);
Record lookup (const Phone); //顶层const 不影响传入函数的对象,所以和上一个等价

Record lookuo(Phone *);
Record lookup(Phone *const); //同上

Record lookup (Phone&);
Record lookup (const Phone&); //新函数,作用于常量引用

Record lookuo(Phone *);      //作用于指向Phone的指针
Record lookup(const Phone*); //新函数, 作用于指向常量的指针

如果形参是某种类型的指针或引用,则通过区分其指向的是常量还是非常量对象可以实现函数重载。此时const 是底层的。
当我们传递一个非常量对象或者指向非常量对象的指针时, 编译器会优先选择非常量版本。

  • const_cast 和重载
    强制类型转换:cast_name(expression); (type 是转换的目标类型; expression 是要转换的值;cast_name 有四种 : stati_cast, const_cast, dynamic_cast, reinterpret_cast)
const string &shortString(const string &s1, const string &s2)
{
    return s1.size() <= s2.size() ? s1 : s2;
} 
const string& ss =  shortString("aaaa", "bbbb"); //函数的参数和返回类型都是const string 的引用。可以对两个非常量的string 实参调用这个而函数, 但是返回的结果是const string的引用

string &shortString(string &s1, string &s2)
{
    auto &r = shortString(const_cast<const string&>(s1),const_cast<const string&>(s2));
    return const_cast<string&>(r);
}//首先将它的实参强制转换成对const的引用, 然后调用了shortString 函数的const 版本, const 返回对const string 的引用, 这个引用事实上绑定在了某个初始的非常量实参上, 因此我们可以将其转换回一个普通的string&, 这显然是安全的。

默认实参

函数中,一旦某个形参被赋予了默认值, 那么它后面所有形参都必须有默认值。
局部变量不能作为默认形参。

内联函数和constexpr函数

内联函数相当于在编译过程中将函数展开。内联机制用于优化规模较小,流程直接,调用频繁的函数。
constexpr函数是指能用于常量表达式的函数,定义constexpr要遵循几条规定,函数的返回值类型及所有形参类型都是字面值类型,而且函数体中必须有且只有一条return语句。
内联函数和constexpr函数可以在程序中多次定义。对于某个给定的内联函数或者constexpr函数来说, 它的多个定义必须完全一致, 基于这个原因,内联函数和constexpr函数通常定义在头文件中。

类型转换

  • 5种转换类型(第四章)
    所有算术类型转换级别都一样。如
void manip (long);
void manip (float);
manip(3.14) //产生二义性

函数指针

函数指针指向的是函数而非对象。

bool lengthCompare (const string &, const string &);
bool (*pf)(const string &, const string &); //未初始化, pf 指向一个函数, 该函数的参数是两个const string的引用, 返回值是bool类型。
// *pf 两端括号不能少。如果不写的话,pf 是一个返回值为bool指针的函数
  • 使用函数指针
    当我们把函数名作为一个值使用时, 该函数自动转换成指针。此外我们还能直接使用指向函数的指针调用该函数,无须提前解引用指针
pf = lengthCompare; 
pf = &lengthCompare; // 两者都是指向名为 lengthCompare 的函数

bool b1 = pf("AA", "BB");
bool b2 = (*pf)("AA", "BB");
bool b3 = lengthCompare("AA", "BB");
  • 重载函数指针
    当我们使用重载函数时, 上下文必须清晰的界定到底应该选用哪个函数,编译器通过指针类型决定到底选用哪个函数,指针类型必须与重载函数中的某一个精准匹配。
  • 函数指针形参
    和数组类似,虽然不能定义函数类型的形参,但是形参可以是指向函数的指针。此时,形参看起来是函数类型,实际上却是当成指针使用。
void useBigger(const string &s1, const string &s2, bool pf (const string &, const string &));
// 第三个形参是函数类型, 它会自动的转换成指向函数的指针。

void useBigger(const string &s1, const string &s2, bool (*pf) (const string &, const string &)); 
//等价的声明: 显式的将形参定义成指向函数的指针。 

//我们也可以直接把函数作为实参使用, 此时它会自动转换成指向函数的指针
useBigger(s1, s2, lengthCompare);

类型别名能让我们简化使用了函数指针的代码

typedef bool (*FuncP)(const string &, const string &);
rtpedef decltype(lengthCompare) * FuncP2; //FuncP 和 FuncP2是指向函数的指针

需要注意的是, decltype 返回函数类型, 此时不会将函数类型自动转换成指针类型,因为decltype 的结果是函数类型, 所以只有在前面加上 * 才能得到指针。

返回指向函数的指针

返回指向函数的指针, 必须把返回类型写成指针的形式,编译器不会自动的将函数的返回类型当成对应的指针类型处理。要想声明一个返回一个返回函数指针的函数, 最简单的办法是使用类型别名

using F = int(int *, int); // F 是函数类型, 不是指针
using PF = int (*) (int *, int); //PF 是指针类型

PF f1(int); //正确,f1 返回类型是指向函数的指针
F f1(int);  //错误,F是函数类型,不能返回一个函数
F *f1(int); //显式的指定返回类型是指向函数的指针

加强理解

//直接声明
int (*f1(int))(int *, int);
//由内而外的顺序,f1是一个函数,返回的是一个指针,指针本身也有形参列表, 因此指针指向函数, 函数的返回类型是int。
posted @   Aaaa_aa  阅读(11)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 上周热点回顾(2.24-3.2)
点击右上角即可分享
微信分享提示