c++Primer 第6章 函数

本文介绍了C++中的函数基础知识,包括形参与实参的关系、局部对象(特别是局部静态对象)的概念。接着讨论了函数声明、分离式编译以及参数传递的不同方式,如传值参数、指针形参和引用形参,强调了const在参数中的作用。此外,详细阐述了数组作为参数的处理,包括数组形参的声明和使用。最后,提到了返回类型、return语句的重要性,以及函数重载的规则和注意事项。
摘要由CSDN通过智能技术生成

6.1 函数基础

形参和实参

  • 实参是形参的初始值.尽管实参和形参存在对应关系,但是并没有规定实参的求值顺序(参见4.1.3,123页).编译器能以任意可行的顺序对实参求值

6.1.1 局部对象

  • 形参和函数体体内定义的变量统称为局部变量
  • 自动对象:只存在于块执行期间的对象.形参是一种自动对象

局部静态对象

  • 可以将局部对象定义成static类型从而获得这样的对象
  • 局部静态对象:在程序的执行路径第一次经过对象定义语句时初始化,并且直到程序终止才能被销毁,在此期间即使对象所在的函数结束执行,也不会对他有影响

6.1.2 函数声明

  • 函数的名字必须在使用之前声明.函数只能定义一次,但可以声明多次
  • 函数的声明不包含函数体,所以也就无需形参的名字

在头文件中进行函数声明

  • 函数应该在头文件中声明在源文件中定义

6.1.3 分离式编译

6.2参数传递

  • 每次调用函数时都会重新创建它的形参,并用传入的实参对其进行初始化

6.2.1 传值参数

指针形参

  • 指针的行为和其他非引用类型一样.当执行指针拷贝操作时,拷贝的是指针的值.拷贝之后,两个指针时不同的值.

6.2.2 传引用参数

使用引用避免拷贝

如果函数无需改变引用形参的值,最好将其声明为常量引用

使用引用形参返回额外信息

6.2.3 const形参和实参

  • 当应实参初始化形参时会忽略掉顶层的const.即形参的顶层const会被过滤掉.当形参有顶层const时,传给它常量或者非常量对象都是可以的.
//上面的话也就是解释了下面的程序为什么不会构成重载.
void f(const int x);
void f(int )x;

指针或者引用形参与const

尽量使用常量引用

  • 把函数不会改变的形参定义成普通引用是一种比较常见的错误,这么做会给函数的调用者一种误导,即函数可以修改他的实参的值,此外,使用普通引用也会极大的限制函数所能接受的实参的类型.如果是普通引用int&只能结果int型的实参不能接受const int型的实参.
find_char(const string &s);
//改为
find_char(string &s);

find_char("wwwww");   // 会报错,error

6.2.4 数组形参

  • 数组的两个性质影响我们定义和使用作用在数组上的函数有影响
    - 不允许拷贝数组.所以我们无法以值传递的方式使用数组参数
    - 使用数组时(通常)将其转换成指针.所有当我们在函数中传递一个数组时,实际上在传递的是数组首元素的指针,数组的大小对函数的调用没有影响.

  • 虽然不能以值传递的方式传递数组,但是我们可以把形参写成类似数组的形式.

// 尽管形式不同,但这个print函数时等价的
// 每个函数都有一个const iht*的形参
void print(const int *);
void print(const int []);
void print(const int [10]);

//每个参数的形参都是const int * 

传递数组大小的三个方法

使用标记指定数组长度
  • 要求数组本身包含一个结束标记,典型例子是C风格的字符串.C风格字符串存储在字符数组中,并且最后一个字符后面跟着一个空字符.
void print(const int * cp)
{
	if(cp){
		while(*cp){
			
		}
	}
}
使用标准库规范
  • 传递指向数组首元素和尾元素的指针.
void print(const int * begin,const int * end)
{
		while(begin != end){
			
		}
	}
}

int main()
{
	int j[2];
	print(begin(j),end(j))
}
显示传递一个表示数组大小的形参
void print(const int * begin,size_type size)

数组形参和const

数组引用形参

  • C++语言允许数组的引用
void print(int (&arr)[10]);
//这样无形中也限制了数组的大小
int &arr[10];  //error,将arr声明为引用的数组
int (&arr)[100] //将qrr声明为数组的引用,即arr是具有10个整数的整型数组的引用.
  • 16.1.1节将会介绍如何呈递任意大小的数组

传递多维数组

  • 在C++语言中没有真正的多维数组,所谓多维数组其实是数组的数组.当将多维数组传递给函数时,真正传递的是指向数组首元素的指针.因为我们处理的是数组的数组,所以首元素本身就是一个数组,指针就是一个指向数组的指针.数组的第二维的大小都是数组的一部分
void print(int (*matrix)[10]); //使用数组的语法进行定义
//声明一个指向含有10个整数构成的数组.编译器会一如既往的忽略掉第一个维度,所以最好不要把他包括在形参列表中

上述等价于

void  print(int matrix[][10]);
//matrix的声明看起来是一个二维数组,实际上形参是指向含有10个整数的数组的指针

习题

在这里插入图片描述

6.3 返回类型和return语句

6.3.1 无返回值函数

6.3.2 有返回值函数

  • return语句返回值的类型必须与函数的返回类型相同,或者能隐式的转换成函数的返回类型.

值是如何被返回的

  • 返回一个值的方式和初始化一个变量或者形参的形式完全一样:返回的值用于初始化调用点的一个临时变量,该临时变量就是函数调用的结果.
  • 例如返回类型为string,意味着返回值将被拷贝到调用点.因此将返回一个变量的副本或者一个未命名的临时string对象.
  • 如果是返回类型是引用,则该引用仅是他所引对象的一个别名.不管是调用函数还是返回结果都不会真正拷贝引用所指的对象.

不要返回局部对象的引用和指针

  • 函数完成后,它所指向的存储空间也随之被释放掉.因此函数终止意味着局部变量的引用将指向不再有效的内存区域.
string test1()
{
	string str = "123";
	return str;
}

string test02()
{
	return "123";
}

上述两个函数都是返回局部对象,都是错误的.

  • 返回局部对象的引用是错误的.返回局部的对象的指针也是错误的.

返回类类型的函数和调用运算符

  • 如果函数返回指针,引用或者类的对象,我们就能使用函数调用的结果访问结果对象的成员.

引用返回左值

  • 函数的返回类型决定函数调用是否是左值,调用一个返回引用的函数得到左值,其他返回类型得到右值.我们可以为返回类型是非常量引用的函数的结果赋值.

列表初始化返回值

  • 函数可以返回花括号包围的值的列表
vector<string> process()
{
    if(....){
        return {};   // 返回一个空vector对象
    }else if(){
        return {"1","2"};
    }else{
        return {"1"};
    }
}
  • 如果函数返回的是内置类型,则花括号包围的列表最多包含一个值.如果函数返回的是类类型,有类本身定义初始值如何使用.

主函数main的返回值

  • 如果函数的返回类型不是void,则必须返回一个值.但是这条规则有关例外.我们允许main函数没有return语句直接结束.但是编译器会自己添加.

6.3.3 返回数组指针

  • 因为数组不能被拷贝,所以函数不能返回数组.但是可以返回数组的指针或者引用.
typedef int arrT[10];
arrT* process(int a[][10]) //返回数组指针
{
    return a;
}

int (*process1(int a[][10]))[10] //返回数组指针,--太过繁琐
{
    return a;
}

int (*process1(int a[][10]))[10];
process先于()结合表明其是一个函数,然后去掉其与括号,剩下的是返回类型:int(*)[10]数组指针

声明一个返回数组指针的函数

  • 返回数组指针的函数形式如下:
    在这里插入图片描述在这里插入图片描述
    在这里插入图片描述

使用尾置返回函数类型

  • C++11为了简化返回类型为数组指针或者数组引用的函数的书写,提供尾置返回类型,任何函数都可以使用尾置返回类型.极大的简化返回类型复杂的函数.为了表示函数真正的返回类型更在形参列表之后,我们在本应该出现返回类型的地方放置一个auto
auto func(int i) -> int(*)[10];
// func接受一个int类型的实参,返回一个指针,该指针指向含有10个整数的数组
// auto test01(int i) ->string (&)[10] 返回的是数组的引用
// auto test01(int i) ->string &[10] 返回的是引用数组,error,不存在引用数组
// auto test01(int i) ->string *[10] 返回的是指针数组
// auto test01(int i) ->string (*)[10] 返回的是数组指针

使用decltype

在这里插入图片描述

习题
int &arr[10];  //error,将arr声明为引用的数组
int (&arr)[100] //将qrr声明为数组的引用,即arr是具有10个整数的整型数组的引用.

在这里插入图片描述

# 使用尾置返回类型
auto test01(int i) -> string (&)[10];
# 使用原始声明
string (&test02(int i))[10];
#使用using进行简化
using refer = string[10];
refer& test03(int i);
# 使用decltype进行
string str[10];
decltype(str) &func();

6.4 函数重载

main函数不能重载

重载与const形参

  • 一个拥有顶层const的形参无法与另外一个没有顶层const的形参区分开来
void loo(Phone);
void loo(const Phone); 


void loo(Phone*);
void loo( Phone * const); 
  • 如果形参是某种类型的指针或引用,则通过区分其指向的是常量对象还是非常量对象可以实现函数重载,此时的const是底层的.
void loo(Account &)
void loo(const Account &) //作用于常量引用


void loo(Account *)
void loo(const Account *) //作用于指向常量的指针
  • 因为const不能转化为其他类型,所以我们只能把const对象传递给const形参.非const两个都可以但是编译器会优化调用非常量版本.

const_cast和重载

const string & shorterString(const string &s1,const string s2)
{
    return s1.size() <= s2.size() ? s1:s2;
}

上面的函数对于常量调用好着,但是两个非常量对象调用却返回常量调用

const string & shorterString(const string &s1,const string s2)
{
    return s1.size() <= s2.size() ? s1:s2;
}
// 上面的函数对于常量调用好着,但是两个非常量对象调用却返回常量调用

string & shorterString( string &s1, string s2)
{
    auto &r = shorterString(const_cast<const string&>(s1),
                            const_cast<const string&>(s2));
    return const_cast<string&>(r);
}

调用重载的函数

  • 当调用重载函数时有三种可能的结果:
  1. 最佳匹配
  2. 无匹配
  3. 二义性调用
int calc(int,int);
int calc (const int,const int);
// 非法,顶层const不影响传入函数的对象,所以一个拥有顶层const的形参无法与一个没有顶层const的形参区分开来.

6.4.1 重载与作用域

在这里插入图片描述

  • 在不同作用域中的函数无法构成重载,因为在C++语言中,名字查找发生在类型检查之前.

6.5 特殊用途语言特性

6.5.1 默认实参

  • 一旦某个形参被赋予了默认值,它后面的所有形参都必须被赋予了默认值.

使用默认实参调用函数

默认实参声明

int test(int a,int b=1);

/*
int test(int a,int b =2)  //error
{

}
 */
int test(int a=1,int b)
{

    return 1;
}
int main() {
    std::cout << "Hello, World!" << std::endl;
    test(1);
    test();
    return 0;
}
  • 对于函数的声明来说,通常的习惯是将其放在头文件中,并且一个函数只声明一次,但是多次声明也是合法的.(即使在头文件中声明在源文件中实现也按照这样规程进行)
  • 在给定的作用域中一个形参只能被赋予一次默认实参,函数的后续声明中只能为之前那些没有默认值的形参添加默认实参.

6.5.2 内联函数和constexptr函数

内联函数可避免函数调用的开销

  • 将函数指定为内联函数,通常就是将它在每个调用点内联的展开.
  • 可以消除调用函数的运行时开销.
    内联说明只是想编译器发出一个请求,编译器可以忽略这个请求

constexpr函数

  • 指能用于常量表达式的函数.
  • 但是有以下约定
    • 函数的返回值类型必须是字面值
    • 所有形参的类型必须是字面值类型
    • 函数中必须有且只有一个return语句
      在这里插入图片描述
      所谓字面值类型:就是可以用来声明constant的类型,这中类型的特点是:简单,值显而易见.

//error:string 不是字面值类型
constexpr int test01(string s)
{

}
  • 指向该初始化任务时,编译器把对constexpr函数的调用替换成其结果值.为了能在编译过程中随时展开,constexpr函数被隐式的指定为内联函数.
  • constexpr函数体内页可以包含其他语句,只要这些语句在运行时不执行任何操作就行.例如:空语句,类型别名,using声明等.
  • 我们允许constexpr函数的返回值并非一个常量:
constexpr int new_sz() {return 42;}

constexpr size_t scale(size_t cnt)
{
    return new_sz() * cnt;
}
int main() {
    std::cout << "Hello, World!" << std::endl;
    constexpr int foo =  new_sz(); // foo是一个常量表达式
    int a = 1;
   // constexpr int f1 = scale(a);  // a不是常量表达式,所以scale(a)不是
    constexpr int f2 = scale(1);  //1 是常量表达式,所以scale(1)也是
    constexpr int f3 = scale(foo);  // foo是常量表达式,所以scale(foo)也是
    return 0;
}

constexpr函数不一定返回常量表达式

把内联函数和constexpr函数放在头文件中

  • 内联函数和constexpr函数可以在程序中多次定义,但是它的多个定义必须完全一致,基于这个原因,内联函数和constexpr函数通常定义在头文件中.

6.5.3 调试帮助

assert预处理宏

assert(exp):首先先对exp求值,如果表达式为假,assert输出信息并终止.
在这里插入图片描述

NDEBUG预处理变量

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

6.6 函数匹配

6.6.1 实参类型转换

在这里插入图片描述

需要类型提升和算术类型转换的匹配

  • 小整数一般都会提升到int类型或者更大的整数类型.假设有两个函数,一个接受int,一个接受short,则只有当调用提供的是short类型的值时才会选择short版本的函数.有时候实参是很小的值也会直接提升成int.
#include <iostream>

using namespace std;

void t(int in) {
    cout << "int____" << endl;
}

void t(short sh) {
    cout << "char___" << endl;
}

int main() {
    // t(3.14); //error
    t(1);   //int____
    t('a'); //int____
    short sh = 1;
    t(sh);   //char___
    std::cout << "Hello, World!" << std::endl;
    return 0;
}

  • 所有算术类型转换的级别都是一样的.例如上面的t(3.14).再入:从int–>unsigned int的转换并不比int–>double的转换级别高
#include <iostream>

using namespace std;

void t(long lon) {
    cout << "long____" << endl;
}

void t(float fl) {
    cout << "float___" << endl;
}

int main() {
    t(3.14); //error  ambiguous
    t(1);   //error  ambiguous
    short sh = 1;
    t(sh);   //error  ambiguous
    std::cout << "Hello, World!" << std::endl;
    return 0;
}

函数匹配和const实参

在这里插入图片描述

习题
#include <iostream>

using namespace std;

void t(int & i,int & j) {
    cout << "int &,int &" << endl;
}

void t(const int & ci,const int & cj) {
    cout << "const int &,const int &" << endl;
}

int main() {
    t(1,2);
    int i,j;
    t(i,j);
    /*
     * 两个函数的区别是是他们的引用类型的形参是否引用了常量,属于底层const.可以将两个函数区分开来
     */
    return 0;
}
#include <iostream>

using namespace std;

void t(char * i,char * j) {
    cout << "char *" << endl;
}

void t(const char * ci,const char * cj) {
    cout << "const char *" << endl;
}

int main() {

    t("=aaa","sdsdd");

    char *a = "aaa";
    t(a,a);
    /*
     * 两个函数的区别是他们的指针类型的形参是否指向了常量
     * ,属于底层const,可以将两个函数区别开来
     */
    return 0;
}

在这里插入图片描述

6.7 函数指针

  • 函数的类型由它的返回类型和形参共同决定.
bool lengthCompare(const string &,const string &);
- 上面函数的类型是:bool (const string &,const string &);
- 要想声明一个可以指向该函数的指针,只需用指针替换函数名即可:
`bool  (pf)(const string &,const string &);`

使用函数指针

  • 当我们把函数名作为一个值使用时,该函数自动的转换成指针.
pf = lengthCompare;
pf = &lengyhCompare; //& 取地址符是可选的.
  • 我还能直接使用指向函数的指针调用该函数,无须提前解引用.
  • 在指向不同函数类型的指针之间不存在转换规则.

重载函数的指针

  • 编译器通过指针类型决定选用那个函数,指针类型必须与重载函数中的某一个精确匹配

函数指针形参

  • 和数组类似,虽然不能定义函数类型的形参,但是形参可以是指向函数的指针.此时,形参看起来是函数类型,实际上却是当成指针使用.
void use(bool  (*pf)(const string &,const string &));// 参数是函数指针类型
void use(bool  (pf)(const string &,const string &));//参数是函数类型

  • 可以使用decltype和typedef来简化
bool lengthCompare(const string &);
typedef bool Func1(const string &);
typedef decltype(lengthCompare) Func2;  //decltype(lengthCompare)返回函数类型,此时不会将函数类型自动转换成指针类型
using Func3 = bool (const string &);
// Func1和Func2,Func3都是执行函数类型


//PFunc1和PFunc2,PFunc3都是指向函数指针的类型
typedef bool (* PFunc1)(const string &);
typedef decltype(lengthCompare) *PFunc2;
using PFunc3 = bool (*)(const string &);
void use(Func1);// 参数是函数指针类型
void use(*Func1);//参数是函数类型



void use(Func1);// 参数是函数指针类型
void use(FPunc1);//参数是函数类型

返回指向函数的指针

在这里插入图片描述

将auto和decltype用于函数指针类型

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值