【C++Primer】第6章:函数

简介: 【C++Primer】第6章:函数

第6章 函数


6.1 函数基础


函数的调用完成两项工作:一是用实参初始化函数对应的形参,二是将控制权转移给被调函数。


被调函数的return语句也完成两项工作:一是返回return语句中的值(如果有的话),二是将控制权从被调函数转移到主调函数。


定义空形参列表


void f1(){}      //隐式的定义形参列表
void f2(void){}  //显式的定义形参列表


函数的返回类型不可以是数组类型或函数类型,但可以是指向数组或函数的指针


形参:函数定义时的参数


实参:函数调用时的参数,函数实际操作对象


局部对象


对于普通局部变量对应的对象来说,当函数的控制路径经过变量定义语句时创建该对象,当到达定义所在的块末尾时销毁它。这种对象叫自动对象


形参是一种自动对象。函数开始时为形参申请存储空间,因为形参定义在函数体作用域之内,所以一旦函数终止,形参也就被销毁。


在所有函数体之外的对象存在于程序的整个执行过程中。此类对象在程序启动时被创建,直到程序结束才会销毁。


局部静态对象在程序执行的路径第一次经过对象定义语句时初始化,并且直到程序终止才会被销毁,在此期间即使对象所在的函数结束执行也不会对它有影响。用static修饰。


函数声明


函数只能定义一次,但是可以声明多次


如果一个函数永远不会被我们用到,那么可以只有声明没有定义


因为函数的声明不包含函数体,所以声明可以不加上形参的名字


6.2 参数传递


传值参数


即值拷贝,


值形参改变不会影响实参


指针形参改变会影响实参


当执行指针拷贝时,拷贝的是指针的值。


传引用参数


形参会修改实参


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


一个函数只能返回一个值


const形参和实参


当形参有顶层const时,实参初始化形参时会忽略掉顶层const,传给它常量对象或者非常量对象都是可以的


void func(const int i){}
void func(int i){}  //错误,重复定义


形参的初始化和变量的初始化是一样的


数组形参


int *matrix[10];   //10个指针构成的数组
int (*matrix)[10];  //指向含有10个整数的数组的指针


//三个等价
void print(const int*);
void print(const int[]);
void print(const int[10]);


数组的引用


void print(int (&arr)[10]){
    for(auto elem : arr){
        cout << elem << endl;
    }
}


含有可变形参的函数


为了编写能处理不同数量实参的函数,C++11新标准提供了两种主要的方法:如果所有实参类型相同,可以传递一个名为initializer_list的标准库类型;如果实参的类型不同,可以编写一种特殊的函数,也就是可变参数模板。


如果函数的形参数量未知但是类型都相同,可以使用initializer_list,其定义在同名的头文件中



initializer<string> ls;  //元素类型是string
initializer<int> li;     //元素类型是int


和vector不一样的是,initializer_list对象中的元素永远是常量值,我们无法改变initializer_list对象中的元素。


6.3 返回类型和return语句


return语句终止当前正在执行的函数并将控制权返回到调用该函数的地方。


return;
return expression;


void函数不要求非得写return语句


不要返回局部对象的引用或指针;调用的函数终止局部变量会释放


引用返回左值


char &get_val(string &str, string::size_type ix){
    return str[ix];   //假定索引值是有效的
}
int main(){
  string s("a value");
    cout << s <<endl;    // a value
    get_val(s,0) = 'A';  //s[0]的值改为A
    cout << s << endl;   // A value
    return 0;
}


shorterString("hi", "bye") = "X";   //错误:返回值是一个常量


列表初始化返回值


C++11新标准规定,函数可以返回花括号包围的值的列表。


主函数main可以没有返回值,编译器会隐式地插入一条返回0的return语句。


main函数不能调用自己


返回数组指针


typedef int arrT[10];   //arrT是一个类型别名,它的类型是含有10个整数的数组
using arrT = int[10];   //同上
arrT* func(int i);      //func返回一个指向含有10个整数的数组的指针


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


尾置返回类型


C++11新标准


//func接受一个int类型的实参,返回一个指针,该指针指向含有10个整数的数组
auto func(int i) -> int(*)[10];


6.4 函数重载


如果同一作用域内的几个函数名字相同但形参列表不同,我们称之为重载函数


main函数不能重载


Record lookup(Phone);
Record lookup(const Phone);  //重复声明


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


函数匹配,即重载确定,有三种情况


  1. 编译器找到一个与实参最佳匹配的函数,并生成调用该函数的代码


  1. 一个都找不到,编译器发出无匹配的错误


  1. 找到了好几个,但都不是明显的最佳选择,此时也将发生错误,称为二义性错误


重载与作用域


如果我们在内层作用域中声明名字,它将隐藏外层作用域中声明的同名实体。




6.5 特殊用途语言特性


默认实参


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


如果想使用默认实参,调用的时候省略该参数就可以


window = screen(, , '?');  //错误:只能省略尾部的实参


在设计含有默认实参的参数时,尽量让不怎么使用默认值的形参出现在前面,而让那些经常使用默认值的形参出现在后面。


默认实参声明


定义只能有一次,声明可以有多次


string screen(sz, sz, char=' ');
string screen(sz, sz, char='*');  //错误:重复声明,试图修改一个已经存在的默认值
string screen(sz=24, sz=80, char=' ');  //正确:添加默认实参


局部变量不能作为默认实参


表达式的类型能转换成形参所需的类型,该表达式就能作为默认实参


内联函数


封装成函数的优点很多,这里就不讲


函数调用的缺点,一次函数调用其实包含着一系列工作:调用前要先保存寄存器,并在返回时恢复;可能需要拷贝实参;程序转向一个新的位置继续执行。


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


将函数指定为内联函数,通常就是将它在每个调用点上“内联地”展开。


一般来说,内联机制用于优化规模较小、流程直接、频繁调用的函数


在编写函数时,在返回类型前面加上关键字inline,就声明成了内联函数



其实很多编译器不支持内联函数,而且一个很长的函数怎么内联展开


constexpr函数


第2章讲的是constexpr变量,这里是函数


constexpr函数指能用于常量表达式的函数


定义constexpr函数,函数的返回值类型及所有形参的类型都得是字面值类型


constexpr int new_sz(){return 42;}  //new_sz()是constexpr函数
constexpr int foo = new_sz();       //正确,foo是一个常量表达式


constexpr函数不一定返回常量表达式,那时候报错而已


内联函数和constexpr函数通常定义在头文件中


6.6 函数匹配


函数匹配有三个步骤:


  1. 确定重载函数集。候选函数两个特征:一是与被调用的函数同名,二是其声明在调用点可见


  1. 确定可行函数。可行函数有两个特征:一是其形参数量与本次调用提供的实参数量相等,二是每个实参的类型与对应的形参类型相同,或者能转换成形参的类型


  1. 确定最佳匹配


如果没找到可行函数,编译器将报告无匹配函数的错误


如果找不到最佳匹配。编译器最终因二义性拒绝调用请求


实参类型转换



void ff(int);
void ff(short);
ff('a');         //char提升成int,调用ff(int)


void manip(long);
void manip(float);
manip(3.14);       //二义性  double存在两种算术类型转换


Record lookup(Account&);       //函数的参数是Account的引用
Record lookup(const Account&);  //函数的参数是一个常量引用
const Account a;
Account b;
lookup(a);   //调用lookup(const Account&)
lookup(b);   //调用lookup(Account&)


6.7 函数指针


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


//比较两个string对象的长度
bool lengthCompare(const string &, const string &);


要想声明一个可以指向该函数的指针,只需要用指针替换函数名即可


//pf指向一个函数,该函数返回值bool类型,两个参数
bool (*pf)(const string &, const string &);  //未初始化


使用函数指针


pf = lengthCompare;   //pf指向名为lengthCompare的函数
pf = &lengthCompare;  //等价  &是可选的


调用函数


bool b1 = pf("hello", "goodbye");
bool b1 = (*pf)("hello", "goodbye");          //等价
bool b1 = lengthCompare("hello", "goodbye");  //等价


同样可以为函数指针赋一个nullptr或值为0的整型常量表达式,表示该指针不指向任何一个函数


函数指针形参


函数指针可以作为形参


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);


直接使用函数指针类型显得冗长而繁琐,可以简化:


//Func和Func2是函数类型
typedef bool Func(const string&, const string&);
typedef decltype(lengthCompare) Func2;   //等价
//FuncP和FuncP2是指向函数的指针
typedef bool(*FuncP)(const string&, const string&);
typedef decltype(lengthCompare) *FuncP2;  //等价


void useBigger(const string&, const string&, Func);
void useBigger(const string&, const string&, FuncP2);  //等价


在第一条语句中,编译器自动地将Func表示的函数类型指针转换成指针


返回指向函数的指针



实例编写两个int加减乘除,返回值为int,vector对象保存指向这些函数的指针,并输出打印每个函数


int add(int a, int b) {
  return a + b;
}
int sub(int a, int b) {
  return a - b;
}
int mul(int a, int b) {
  return a * b;
}
int main() {
  //typedef int(*p)(int a, int b);
  using p = int(*)(int, int);    //等价
  vector<p> v = { add,sub,mul };
  for (auto f : v) {
    cout << f(2, 2) << endl;
  }
  system("pause");
  return 0;
}


目录
相关文章
|
19天前
|
程序员 C++ 容器
在 C++中,realloc 函数返回 NULL 时,需要手动释放原来的内存吗?
在 C++ 中,当 realloc 函数返回 NULL 时,表示内存重新分配失败,但原内存块仍然有效,因此需要手动释放原来的内存,以避免内存泄漏。
|
30天前
|
存储 前端开发 C++
C++ 多线程之带返回值的线程处理函数
这篇文章介绍了在C++中使用`async`函数、`packaged_task`和`promise`三种方法来创建带返回值的线程处理函数。
43 6
|
30天前
|
C++
C++ 多线程之线程管理函数
这篇文章介绍了C++中多线程编程的几个关键函数,包括获取线程ID的`get_id()`,延时函数`sleep_for()`,线程让步函数`yield()`,以及阻塞线程直到指定时间的`sleep_until()`。
21 0
C++ 多线程之线程管理函数
|
1月前
|
编译器 C语言 C++
C++入门3——类与对象2-2(类的6个默认成员函数)
C++入门3——类与对象2-2(类的6个默认成员函数)
23 3
|
1月前
|
编译器 C语言 C++
详解C/C++动态内存函数(malloc、free、calloc、realloc)
详解C/C++动态内存函数(malloc、free、calloc、realloc)
130 1
|
1月前
|
存储 编译器 C++
C++入门3——类与对象2-1(类的6个默认成员函数)
C++入门3——类与对象2-1(类的6个默认成员函数)
25 1
|
1月前
|
编译器 C语言 C++
C++入门6——模板(泛型编程、函数模板、类模板)
C++入门6——模板(泛型编程、函数模板、类模板)
38 0
C++入门6——模板(泛型编程、函数模板、类模板)
|
2月前
|
编译器 C++
【C++核心】函数的应用和提高详解
这篇文章详细讲解了C++函数的定义、调用、值传递、常见样式、声明、分文件编写以及函数提高的内容,包括函数默认参数、占位参数、重载等高级用法。
22 3
|
3月前
|
编译器 C++ 容器
【C++】String常见函数用法
【C++】String常见函数用法
|
3月前
|
C++
c++常见函数及技巧
C++编程中的一些常见函数和技巧,包括生成随机数的方法、制表技巧、获取数字的个位、十位、百位数的方法、字符串命名技巧、避免代码修改错误的技巧、暂停和等待用户信号的技巧、清屏命令、以及避免编译错误和逻辑错误的建议。
32 6