0


【c ++ primer 笔记】第6章 函数

在这里插入图片描述

🎉作者简介:👓

      博
     
     
      主
     
     
      在
     
     
      读
     
     
      机
     
     
      器
     
     
      人
     
     
      研
     
     
      究
     
     
      生
     
     
      ,
     
     
      目
     
     
      前
     
     
      研
     
     
      一
     
     
      。
     
     
      对
     
     
      计
     
     
      算
     
     
      机
     
     
      后
     
     
      端
     
     
      感
     
     
      兴
     
     
      趣
     
     
      ,
     
     
      喜
     
     
      欢
     
     
      c
     
     
      +
     
     
      +
     
     
      ,
     
     
      g
     
     
      o
     
     
      ,
     
     
      p
     
     
      y
     
     
      t
     
     
      h
     
     
      o
     
     
      n
     
     
      ,
     
     
      目
     
     
      前
     
     
      熟
     
     
      悉
     
     
      c
     
     
      +
     
     
      +
     
     
      ,
     
     
      g
     
     
      o
     
     
      语
     
     
      言
     
     
      ,
     
     
      数
     
     
      据
     
     
      库
     
     
      ,
     
     
      网
     
     
      络
     
     
      编
     
     
      程
     
     
      ,
     
     
      了
     
     
      解
     
     
      分
     
     
      布
     
     
      式
     
     
      等
     
     
      相
     
     
      关
     
     
      内
     
     
      容
     
    
   
   
    \textcolor{orange}{博主在读机器人研究生,目前研一。对计算机后端感兴趣,喜欢c++,go,python,目前熟悉c++,go语言,数据库,网络编程,了解分布式等相关内容}
   
  
 博主在读机器人研究生,目前研一。对计算机后端感兴趣,喜欢c++,go,python,目前熟悉c++,go语言,数据库,网络编程,了解分布式等相关内容

📃

      个
     
     
      人
     
     
      主
     
     
      页
     
     
      :
     
    
   
   
    \textcolor{gray}{个人主页:}
   
  
 个人主页: 小呆鸟_coding

🔎

      支
     
     
      持
     
     
      :
     
    
   
   
    \textcolor{gray}{支持:}
   
  
 支持:
 
  
   
    
     
      如
     
     
      果
     
     
      觉
     
     
      得
     
     
      博
     
     
      主
     
     
      的
     
     
      文
     
     
      章
     
     
      还
     
     
      不
     
     
      错
     
     
      或
     
     
      者
     
     
      您
     
     
      用
     
     
      得
     
     
      到
     
     
      的
     
     
      话
     
     
      ,
     
     
      可
     
     
      以
     
     
      免
     
     
      费
     
     
      的
     
     
      关
     
     
      注
     
     
      一
     
     
      下
     
     
      博
     
     
      主
     
     
      ,
     
     
      如
     
     
      果
     
     
      三
     
     
      连
     
     
      收
     
     
      藏
     
     
      支
     
     
      持
     
     
      就
     
     
      更
     
     
      好
     
     
      啦
     
    
   
   
    \textcolor{green}{如果觉得博主的文章还不错或者您用得到的话,可以免费的关注一下博主,如果三连收藏支持就更好啦}
   
  
 如果觉得博主的文章还不错或者您用得到的话,可以免费的关注一下博主,如果三连收藏支持就更好啦👍
 
  
   
    
     
      就
     
     
      是
     
     
      给
     
     
      予
     
     
      我
     
     
      最
     
     
      大
     
     
      的
     
     
      支
     
     
      持
     
     
      !
     
    
   
   
    \textcolor{green}{就是给予我最大的支持!}
   
  
 就是给予我最大的支持!🎁

💛本文摘要💛
本专栏主要是对c++ primer这本圣经的总结,以及每章的相关笔记。目前正在复习这本书。同时希望能够帮助大家一起,学完这本书。 本文主要讲解第6章 函数

文章目录

  • 本章主要介绍函数传参及函数返回结果、函数重载、重载函数应该选择哪个进行调用。

🎯6.1 函数基础

  • 通过调用运算符 () 来执行函数。

函数调用完成俩项工作:

  • 用实参初始化函数对应的形参。
  • 将控制器转移给被调用函数。

此时主调函数被中断,被调函数开始执行。

函数执行的第一步:隐式地定义并初始化它的形参。实参是形参的初始值,第一个实参初始化第一个形参。

return 语句完成俩项工作:

  • 返回return 语句中的值
  • 将控制权从被调函数转移回主调函数

形参

  • 实参是形参的初始值。
  • 实参的类型与形参类型匹配,或者可以转换成形参类型。
  • 可以没有形参名,但是函数无法使用未命名的形参,即使形参未命名,也要传入实参。

返回类型

  • 函数返回类型不能是数组类型或者函数类型.
  • 但是可以是数组指针函数指针

🎨6.1.1 局部对象

  • 名字有作用域,对象有生命周期
  • 形参和函数体内部定义的变量统称为局部变量

形参属于自动对象

  • 函数开始时为形参申请存储空间,因为形参定义在函数体作用域之内,函数终止,形参被销毁
  • 在所有函数外定义的对象存在于程序的整个执行过程中

局部静态变量

  • 在程序的执行路径第一次经过对象定义语句时初始化,直到程序终止被销毁。
  • 只存在于块执行期间的对象。当块的执行结束后,它的值就变成未定义的了。
  • 如果想要局部变量声明周期在整个程序结束,可以将局部变量定义成static类型
  • 如果局部静态变量没有显式的初始值,将执行值初始化。

🎨6.1.2 函数声明

  • 函数的声明和定义唯一的区别是声明无需函数体,用一个分号替代。
  • 函数声明主要用于描述函数的接口,也称函数原型。
  • 函数的声明无需形参的名字(因为声明不包含函数体)。
  • 函数的三要素:返回类型函数名形参类型
  • 建议在头文件中声明函数,在源文件中定义函数。

🎨6.1.3 分离式编译

分离式编译允许把程序分割到几个文件中,每个文件独立编译。

🎯6.2 参数传递

  • 引用传递:又称传引用调用,指形参是引用类型,引用形参是它对应的实参的别名。
  • 值传递:又称传值调用,指实参的值是通过拷贝传递给形参。
  • 形参初始化的机理和变量初始化一样。

🎨6.2.1 传值参数

  • 当初始化一个非引用类型的变量时,初始值被拷贝给变量。
  • 函数对形参做的所有操作都不会影响实参
  • 指针形参:C++建议使用引用类型的形参代替指针

🎨6.2.2 传引用参数

  • 通过使用引用形参,允许函数改变一个或多个实参的值。
  • 通过引用可以避免拷贝(如果对象太长,可以使用引用来避免拷贝,拷贝太浪费内存了)
  • 如果无需改变引用形参的值,最好将其声明为常量引用
  • 引用形参直接关联到绑定的对象,而非对象的副本。
  • 使用引用形参可以用于返回额外的信息。
//既返回位置也返回次数,此时可以给函数传入一个额外的引用实参,令其保存字符出现的次数
string::size_type find_char(const string &s,char c, string::size_type &occurs){auto size = s.size();
    occurs =0;for(int i =0; i != s.size(); i ++){if(s[i]== c){if(ret == s.size()) ret = i;++ occurs;}}return ret;//出现次数通过occurs隐式地返回}

🎨6.2.3 const 形参和实参

  • 实参初始化形参时以及进行拷贝时,都会忽略掉顶层const,因此导致void func (const int i);调用既可以传入const int也可以传入int

加深理解:引用是没有顶层 const 的,因此顶层 const 适用于指针及其他类型,对于传值来说传递的是实参的副本,无论如何都不会改变实参,因此形参加不加顶层 const 都是一样的。

voidfcn(constint i ){}voidfcn(int i ){}//错误:重复定义了fcn(int)

指针或引用形参与const

  • 使用非常量初始化一个底层const对象,但是反过来不行。
  • 普通引用必须用同类型的对象初始化。

加深理解:不能把普通引用绑定到const 对象上,不能把普通的引用绑定在字面值上。

voidreset(int&r){};constint i =10;reset(i);//错误:不能把普通引用绑定到const 对象上reset(42);//错误:不能把普通的引用绑定在字面值上

尽量使用常量引用

  • 尽量使用常量引用做形参(注意常量引用的 const 是底层 const。)
  • 可以用字面值初始化常量引用

使用常量引用的优点

  1. 想要调用引用版本的reset,只能使用int 类型对象。而不能使用字面值、求值结果为int 的表达式、需要转换的对象或者const int 类型的对象。
  2. 想要调用指针班的reset 只能使用 int *
不能把 const 对象、字面值或需要类型转换的对象传递给普通的引用形参。但是可以传递给常量引用形参。

注意: 如果函数 a 把形参定义为了常量引用,函数 b 形参是普通引用,那么不能在 a 中使用 b 调用该常量引用形参。

bool  is_sentence(const string &s){//如果在s的末尾有且只有一个句号,则s是一个句子
    string :: size_type ctr =0;returnfind_char(s,'.', ctr)== s.size()-1&& ctr ==1;}

错误因为is_sentence是const对象而find_char是普通对象

🎨6.2.4 数组形参

数组的两个特殊性质:

不允许拷贝数组、使用数组时常会将其转换成指针
  • 因为数组不能进行拷贝,所以无法使用值传递的方式使用数组参数。因为数组会被转换成指针,所实际上传递的是指向数组的首元素指针。
voidprint(constint*);voidprint(constint[]);voidprint(constint[10]);//三种声明等价,数字 10 没有什么实际影响int i=0, j[2]={0,1};printf(&i);//正确:&i的类型是int *printf(j);//正确:j转换成int * 并指向j[0]//传入的是一个数组,那么实参会自动转换成指向数组首元素的指针

使用数组做形参确保数组访问不越界的方法:

  • 使用一个结束标记指定数组已结束,典型代表为 C 风格字符串
  • 传递指向数组首元素和尾后元素的指针
  • 显示传递一个表示数组大小的形参

数组引用形参

  • 可以定义数组的引用,但是没有引用数组,因为引用不是一个对象。注意数组的大小是构成数组类型的一部分
func(int&arr[10]);//错误:arr 是引用的数组func(int(&arr)[10]);//正确:array 是包含 10 个整数的整型数组的引用int*arr[10];//指针的数组int(*arr)[10];//指向数组的指针

🎨6.2.5 main:处理命令行选项

  • int main(int argc, char *argv[]){...}
  • 第一个形参代表数组中字符串的数量;第二个形参argv是一个数组,它的元素是指向C风格字符串的指针

🎨6.2.6 含有可变形参的函数

无法预知应该向函数传递几个实参,处理不同数量实参的主要方法有俩种:

  • 如果所有实参类型相同,传递一个 initializer_list 类型
  • 使用省略符形参,它可以传递可变数量的实参,注意它一般仅用于与 C 函数交互的接口程序

initializer_list 形参特点

  • initializer_list 也是一种模板类型,定义在同名的头文件中。
  • initializer_list 与 vector 容器大致相同,但是它的元素都是常量值
  • initializer_list 对象只能使用花括号初始化
  • C++ 里的 vector 等各类容器使用列表初始化时本质上都是通过一个采用了 initializer_list 形参的构造函数进行初始化的。
    操作解释
    initializer_list<T> lst;
    
    默认初始化;
    T
    
    类型元素的空列表
    initializer_list<T> lst{a,b,c...};
    
lst

的元素数量和初始值一样多;

lst

的元素是对应初始值的副本;列表中的元素是

const

lst2(lst)

拷贝或赋值一个

initializer_list

对象不会拷贝列表中的元素;拷贝后,原始列表和副本共享元素。

lst2 = lst

同上

lst.size()

列表中的元素数量

lst.begin()

返回指向

lst

中首元素的指针

lst.end()

返回指向

lst

中微元素下一位置的指针

voiderr_msg(ErrCode e, initializer_list<string> il){
    cout << e.msg << endl;for(auto bed = il.begin(); beg != il.end();++ beg)
        cout <<*beg <<" ";
    cout << endl;}err_msg(ErrCode(404),{"functionX", "okay});

如果向 initailizer_list 形参中传递一个值的序列,必须把序列放在花括号里。

voidfunc(initializer_list<int> il)func({3,4,5,2});

省略符形参

  • 省略符形参仅用于 C 和 C++ 通用的类型,大多数类类型的对象传递给省略符形参都无法正确拷贝。
  • 省略符形参只能出现于形参列表的最后一个位置
voidfunc(parm_list,...);voidfunc(...);

🎯6.3 返回类型和 return 语句

  • 与参数传递一样,不能返回数组,只能返回指向数组的指针或数组的引用

return 俩个作用:

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

🎨6.3.1 无返回值函数

  • 没有返回值的 return语句只能用在返回类型是 void的函数中
  • 返回 void的函数不要求非得有 return语句。因为它会在最后一句后面隐式地执行 return。

🎨6.3.2 有返回值函数

  • return语句的返回值的类型必须和函数的返回类型相同,或者能够隐式地转换成函数的返回类型
  • 在含有 return 语句的循环和条件后面也应该有一条 return 语句
  • 返回一个值和初始化一个变量或形参的方式一样,返回的值用于初始化调用点的变量。
  • 不要返回局部对象的引用和指针

因为局部对象在函数调用完成后,他所占用的存储空间被释放,此时函数终止意味着局部变量的引用将指向不再有效的内存区域

const string&func(){
    string s;
    s ="xiaodainiao"return s;//错误,字符串字面值转换成一个局部临时变量,不能返回局部对象的引用}
bool str_cmp(const string &str1,const string &str2){for(int i =1; i < n; i ++){if(str[1]!= str[2])return;//错误}return true;//这个往往会被忽略,在含有 return 语句的循环和条件后面也应该有一条 return 语句}

引用返回左值

  • 返回引用的函数返回的是左值,其他返回类型得到右值
  • 可以为返回类型是非常量引用的函数的结果赋值
char&get_val(string &str, string::size_type ix){return str[ix];}get_val(s,0)='A';

列表初始化返回值

vector<string>process(){
    string s;if(expected.empty())return{};// 返回一个空 vector 对象elseif(expected == actual)return{"funcitonX","okay"};//返回一个列表初始化的 vector 对象    }

main 的返回值

  • 如果结尾没有return,编译器将隐式地插入一条返回0的return语句
  • 返回0代表执行成功,返回其它值代表失败。
  • cstdlib 头文件定义了两个预处理变量来表示成功与失败
return EXIT_FAILURE;//失败return EXIT_SUCCESS;//成功

🎨6.3.3 返回数组指针

  • 函数不能返回数组,但是可以返回数组的指针或引用
int*p[10];// 错误,指针的数组,p是一个含有10个指针的数组int(*p)[10];// 正确,数组的指针,p是一个指针,指向含有10个整数的数组int&p[10];//错误,引用数组,引用不是对象,没有引用的数组,有数组的引用int(&p)[10];// 正确,数组引用
int(*function(int i )[10]);
  • func(int i ) 表示调用func函数需要传递一个int 实参
  • (*func(int i )) 对函数调用的结果执行解引用
  • (*function(int i )[10])表示解引用结果是一个大小为10的数组
  • int(*function(int i )[10])表示数组中的元素是int 类型

有三种方法简化返回数组指针或引用。

  • 使用类型别名
  • 使用尾置返回类型
  • 使用 decltype

使用类型别名

typedefint arrT[10];// arrT 表示含有 10 个整数的数组
using arrT =int[10];//等价声明
arrT*func(int i);// 函数 func 返回一个指向含有 10 个整数的数组的指针

使用尾置返回类型

  • 任何函数的定义都可以使用尾置返回,适用于返回类型复杂的函数
autofunc(int i)->int(*)[10];//func接受一个int 类型的实参,返回一个指针,该指针指向含有10个整数的数组

使用 decltype

  • 如果已知函数返回的指针将指向哪个数组,可以使用 decltype
int odd[]={1,3,5,7,9};decltype(odd)*func(int i);//返回一个数组指针

**注意 decltype 的结果是一个数组,要想表示func返回指针还必须在函数声明时加一个

*

号。**

🎯6.4 函数的重载

  • main 函数不能重载
  • 函数名字相同,但是形参数量形参类型不同,此时发生重载。
  • 函数返回值类型不同,不是函数重载

重载和const形参

  • 函数重载无法区分顶层 const 形参和非顶层 const 形参,但是可以区分底层 const 形参与普通形参
intfunc(int i);intfunc(constint i);//顶层const,无法区分,相当于重复声明intfunc(int* p);intfunc(int*const p);//顶层const,无法区分,相当于重复声明intfunc(int* p);intfunc(constint* p);//底层const,可以区分,一个普通指针,一个常量指针intfunc(int& i);intfunc(constint& i);//底层const,可以区分,一个普通引用,一个常量引用

const_cast

  • 强制类型转化,可以将常量引用转换为非常量引用,也可以将非常量引用转换为常量引用
  • 当要重载常量引用与非常量引用的版本时,在非常量引用的版本中可以通过 const_cast 将参数和返回值从常量引用转换为非常量引用,以实现对常量引用版本的调用。
string& s;
const_cast <const string&>(s);// 将 s 转换为常量引用
const_cast <string&>(s);// 将 s 转换回非常量引用const string &str(const string &s1,const string &s2){return s1.size()> s2.size();}

 string &str( string &s1,  string &s2){auto&r =str(const_cast<const string&>(s1), const_cast<const string&>(s2))return const_cast<string&>(r);}
  1. 将实参转换为const的引用,然后调用str的const版本。
  2. 当返回时,因为返回类型是非常量引用,所以需要再次转换一下。

6.4.1 重载与作用域

  • 不同的重载版本要定义在同一作用域中(一般都是全局)

🎯6.5 特殊用途语言特性

🎨6.5.1 默认实参

  • 一旦某个形参被赋予了默认值,那么它之后的形参都必须要有默认值。(反过来就是设置默认值的形参必须都放在没有默认值的形参后面。)
string screen(sz ht =24, sz wid =80,char backgrnd =' ');
  • 如果想使用默认值实参时,只要在调用该函数的时候省略该实参就可以(默认实参负责填补函数调用时缺少的尾部实参。)
  • 局部变量不能作为默认实参,全局变量和字面值都可以。
  • 如果函数有默认实参,则调用函数时传入的实参数量可能少于它实际使用的实参数量
  • 通常应该在函数声明中指定默认实参,并将该声明放在合适的头文件中
string screen(sz, sz,char=' ');
string screen(sz, sz ,char='*');//错误重复声明,不能修改已经存在的默认值

🎨6.5.2 内联函数和constexpr函数

  • 内联函数可避免函数调用时的开销
  • 内联函数适合于规模小、流程直接、频繁调用的函数。

使用函数的缺点

  • 调用函数更慢,调用前要先保存寄存器(保护现场),并在返回时恢复:可能需要拷贝实参;程序转向一个新的位置继续执行。

在函数前用 inline 声明一下即表明是内联函数

🎨constexpr函数

  • 只能用于常量表达式的函数。但是 constexpr 函数不一定返回常量表达式。
  • constexpr int new_sz() {return 42;}
  • constexpr 函数的返回类型及所有的形参类型都必须是字面值类型,函数体中必须有且只有一条 return 语句
  • constexpr函数应该在头文件中定义。
  • 应该把内联函数和constexpr函数的定义放到头文件里。

🎯6.6 函数匹配

  • 重载函数匹配的三个步骤:1.候选函数;2.可行函数;3.寻找最佳匹配。
  • 候选函数:选定本次调用对应的重载函数集,集合中的函数称为候选函数(candidate function)。
  • 可行函数:考察本次调用提供的实参,选出可以被这组实参调用的函数,新选出的函数称为可行函数
  • 寻找最佳匹配:基本思想:实参类型和形参类型越接近,它们匹配地越好。

调用重载函数应尽量避免强制类型转换。

🎨6.6.1 实参类型转换

实参类型到形参类型的转换分为几个等级,具体排序如下:

  1. 精确匹配 实参类型和形参类型相同 从数组类型或函数类型转化为对应的指针类型 向实参中添加顶层const或删除顶层const
  2. 通过const转换实现的匹配。
  3. 通过类型提升实现的匹配
  4. 通过算术类型转换或指针转换实现的匹配
  5. 通过类类型转换实现的匹配

注意:所有算数类型转换的级别都一样。

🎯6.7 函数指针

  • 函数指针指向的是函数而非对象
  • 一种函数指针只能指向一种特定的函数类型:
bool Compare(const string&,const string&);// 此函数的类型是 bool(const string&, const string&);bool(*pf)(const string&,const string&);// 声明了一个指向 bool(const string&, const string&) 类型函数的指针,注意括号不能少;
pf = Compare;// 给指针赋值,指向 Compare 函数
pf =&Compare;// 这两种赋值语句完全等价
bool b1 =pf("hello","goodbye");// 可以直接使用指针替代函数名调用函数。
bool b2 =(*pf)("hello","goodbye");// 与上面的等价
bool b3 =Compare("hello","goodbye")//与上面等价
  • 当把函数名作为一个值使用时,函数自动地转换成指针
  • 不同函数类型的指针间不能相互转换。函数指针也可以指向 nullptr 或 0
  • 对于重载函数,指针类型必须与重载函数中的某一个精确比配。
string::size_type str(cosnt string&,const strint&)
bool str1(constchar*,constchar*)
pf =0;
pf = str;//错误,返回类型不匹配
pf = str1;//错误,形参类型不匹配
pf = Compare;//正确,函数和指针类型精确匹配

函数指针形参

  • 函数不能作形参,但是函数指针可以做形参,之后在调用时可以直接传入函数名作实参
  • 函数名做形参也会自动的转换为指针。

类似于数组不能做形参,但是数组指针可以做形参

bool GetBigger(const string& s1,const string& s2,bool(*comp)(const string&,const string&));// 函数指针做形参GetBigger(s1, s2, Compare);// 实参直接传入函数名 Compare 

返回函数指针

  • 不能返回一个函数,但是可以返回函数指针(注意这时函数名不会自动转换为函数指针)

类似于返回数组,数组不能返回,只能返回指向数组的指针

  • 声明一个返回函数指针的函数有几种方法,其中直接声明最麻烦,使用尾置类型和 decltype 更简单一些,但是最好使用类型别名。
'直接声明'bool(*f1(int))(double);// f1 是一个函数,函数的形参列表为 int,返回类型为函数指针。这个函数指针的类型为 bool (*)(double),即形参为 double,返回类型为 bool。'使用尾置类型'autof1(int)->bool(*)(double);'使用 decltype'
bool func(double);decltype(func)*f1(double);'使用类型别名'
using PF =bool(*)(double);
PF f1(int);

和数组的类似,可以进行对比

typedefint arrT[10];// arrT 表示含有 10 个整数的数组
using arrT =int[10];//等价声明
arrT*func(int i);// 函数 func 返回一个指向含有 10 个整数的数组的指针autofunc(int i)->int(*)[10];//func接受一个int 类型的实参,返回一个指针,该指针指向含有10个整数的数组int odd[]={1,3,5,7,9};decltype(odd)*func(int i);//返回一个数组指针

本文转载自: https://blog.csdn.net/weixin_45043334/article/details/125751253
版权归原作者 小呆鸟_coding 所有, 如有侵权,请联系我们删除。

“【c ++ primer 笔记】第6章 函数”的评论:

还没有评论