C++学习笔记05--函数

01函数基础

函数:可以多次调用的一段代码;

        函数头: 函数名称  形式参数  返回类型

        函数体:包含具体逻辑的语句块

函数声明:声明只包含函数头,不包含函数体,通常放在头文件中,函数声明可以多次定义

函数定义:函数定义通常只能定义一次  -----一次定义原则    可以多次声明  

                函数定义对应一段汇编代码  若多次定义编译器不知如何选

int add(int x,int y);   声明此处有;

int main()
{
    add(2,3);       编译器顺序执行  所以需要声明

}

int add(int x,int y)
{
    return x+y;

}

函数调用:

        需要提供函数名与实际参数

        实际参数拷贝初始化形式参数

        返回值会拷贝给函数调用者

        栈 帧 结构 ( stack  frame )

栈帧结构(程序调用栈结构)详解_途~乐的博客-CSDN博客_栈帧结构栈帧结构(程序调用栈结构)详解知识点扫描ESP:栈指针寄存器(extended stack pointer),放着一个指针,该指针永远指向系统栈最上面一个栈帧(栈帧不理解没关系)的栈顶。EBP:基址指针寄存器(extended base pointer),放着一个指针,该指针永远指向系统栈最上面一个栈帧的底部。EIP:指令寄存器(extended instruction pointer),存储CPU下一个要执行的指令的地址。简介​栈帧结构就是在程序运行中发生函数调用时,会产生一个调用栈,调用栈https://blog.csdn.net/weixin_41355162/article/details/109478638

函数的外部链接:

nm 可执行程序        ----------查看程序的外部链接

nm 可执行程序 | c++filt -t    ------可视化信息

#include<iostream>
using namespace std;

                            将add函数编译为C语言形式的链接   用nm a.out   查看                      
 extern "C"                 nm a.out | c++filt -t  过滤为C++方式的信息
int Add(int x,int y)
{
    return x+y;
}                         C++有函数重载 编译为链接时函数名会被处理,对C语言兼容不友好
                           extern "C"将函数编译为支持C语言的链接
int Sub(int x,int y)
{
    return x-y;
}

int main()
{
    int Z=Add(2,3);
    int Q=Sub(2,3);
    cout<<Z<<endl;
    cout<<Q<<endl;

}

02函数详解

参数

函数的参数可以包含零或者多个形参.

        包含0个形参可以用void标记  或者空      void  fun()/void fun(void)

        对于非模板函数,每个形参可以没有名称,但是必须有确定的类型,  void fun(int,int){.....   }

        形参名称的变换不会引入函数不同的版本   类型的变化会引入函数重载

        实参到形参的拷贝求值顺序不一定     c++17强制省略复制临时对象

void fun(int x,int y){}

int main()
{
    fun(1,2)   1 2拷贝初始化x  y由编译器决定  使性能最好
}

函数传值    传址   传引用

传值
void fun(int par)     int par=arg  
{
    ++par;
}

int main()
{
    int arg=3;
    fun(arg);           arg不会改变
    cout<<arg<<endl;
}

传址
void fun(int* par)     int *par=&arg  
{
    ++par;
}

int main()
{
    int arg=3;
    fun(&arg);           arg改变
    cout<<arg<<endl;
}

传引用
void fun(int& par)     int &par=arg  
{
    ++par;
}

int main()
{
    int arg=3;
    fun(arg);           arg改变
    cout<<arg<<endl;
}

函数传参数过程中会引入类型的退化

void fun(int* par){....}

int a[3];
fun(a)   a退化为指针

void fun(int (&par)[3]){....}

int a[3];
fun(a)     加入引用  防止a退化为指针

变长参数:

        initializer_list

        可变长度模板参数

        使用省略号表示形式参数

void fun(std::initializer_list<int>par){........}

fun({1,2,3,4,5});
fun({1,2,3,"abc",4});  error  类型错误

initializer_list  本质是一个指针 指向开头元素和结尾后一个元素

缺省实参的定义:

1.如果函数具有缺省实参,那么右侧的形参都必须具有缺省实参   原因见3

        void fun(int x,int y=1,int z=9){}

         void fun(int x,int y=1,int z){}  error

2.在一个翻译单元中,每个形参的缺省实参只能定义一次

        (声明和定义只能定义一次   一般定义在声明中)

        void fun(int x,int y=2,int z=9); void fun(int x,int y,int z){.........}

        void fun(int x,int y=2,int z=9); void fun(int x,int y=2,int z=9){.........}   error

        因为函数满足  一次定义原则  可以多次声明  所以

        void fun(int x,int y,int z=9); void fun(int x,int y=2,int z); void fun(int x,int y,int z){........}        3.具有缺省实参调用时,传入实参会从左到右的顺序匹配形参

        (所以1必须满足)

         void fun(int x,int y=1,int z=9){}    fun(1,2,3);从左到右一次匹配  

 4.缺省实参为对象时,实参的缺省值会岁对象的变化而变化

        int x=3;   void fun(int y=x){cout<<y<<'\n';}       int x=4;fun();  ----3

         int x=3;   void fun(int y=x){cout<<y<<'\n';}        x=4;fun();  ----4

        int x=3;   void fun(int y=x){cout<<y<<'\n';}      fun();  ----3

main.cpp

#include<iostream>
using namespace std;
#include"header.h"

void source();
void fun(int x,int y,int z)  //实参缺省值  写在声明中  函数根据声明中的值  执行函数体的内容
{
    cout<<x+y+z<<endl;
}
int main()
{
    fun(1);     -----3
    source();   -----6
}

source.cpp

void fun(int x,int y=2,int z=2);
void source()
{
    fun(2);
}


header.h
void fun(int x,int y=1,int z=1);

int argc  char**argv/ char *argv[ ] 

argc  非负数 表示从程序运行环境传递给程序的实参个数

argv   数组指针   指向一个包含argc+1个元素的数组的首地址  数组末尾元素是空地址  

主函数 - cppreference.comhttps://zh.cppreference.com/w/cpp/language/main_function./demo      1               2          3        nullptr

argv[0]  argv[1]   argv[2]  argv[3]   argv[4]

#include<iostream>
using namespace std;

int main(int argc,char* argv[])
{
    cout<<"argc="<<argc<<endl;//argc 执行程序时表示输入的个数
    for(int i=0;i<argc;++i)
    {
        cout<<*(argv+i)<<'\t';  
        cout<<argv[i]<<endl;  //argv[i]指针索引  argv是一个指针 
                                指向了一个 argc+1 大小的数组
    }
}

函数体

函数体形成域:

        形成语句块  

函数体执行完成时的返回:        

1. 隐式返回 

         void fun() {     }    fun();   

 2.显示返回    return 

        return ;  

        return 表达式;

        return 初始化列表

void fun()
{
    cout<<"Hello"<<endl;
    return ;     显示返回空语句
    cout<<"world<<endl;   不再执行
}

int fun()
{
    cout<<"Hello"<<endl;
    return 1;     返回表达式
}

std::vector<int>fun()
{
    return {1,2,3,4,5};  返回列表

}

小心返回时自动对象的引用或指针

int &fun()
{
    int x=9;
    return x;
}
int &ref=fun();  error  返回值x只生存在fun中   ref绑定了一个即将销毁的对象

int *fun()
{
    int x=9;
    return &x;
}
int *ptr=fun();  error  返回值x只生存在fun中   ptr指向了一个即将销毁的对象

将int x=9;变为   static  int x=9   则不再报错   x是一个局部静态变量 在程序执行完才销毁

返回类型

返回类型表示函数计算结果的类型   可以为void 

返回类型的书写方法:

 1.位于函数头的前部----经典写法

2.C++11引入:位于函数头的后部

3.C++14引入:返回类型的自动推导     可以使用constexpr  if 构造具有不同返回类型的函数

auto fun(int a,int b)->int   位于函数头后部
{
    return a+b;
}

auto fun(int a,int b)   自动推导类型
{
    return a+b;
}

decltype(auto) fun(bool input)
{
    int x=3;
    return x;  ----int类型
    return (x);  ---(x)为表达式  左值   为int&类型


constexpr bool value=false;  constexpr定义一个编译期的常量
auto fun()
{
    if constexpr (value)   编译器决定返回不同的类型
    {
        return 1;
    }
    else
    {    
        return 3.14;
    }

返回类型与结构化绑定  ---语法糖  c++17

struct Str
{
    int x;
    int y;
}

Str fun()
{
    return Str();
}

int mian()
{
    Str s;
    s=fun();
    s.x
    s.y   ---通常

    auto &[v1,v2]=fun();
       v1;
       v2;
}

[[nodiscard]]属性   c++17

[[nodiscard]] int fun(int a,int b)
{
    return a+b;
}

int main()
{
    int x=fun(2,3);   有了[[nodiscard]]说明函数返回很重要 必须接受  否则报错
    cout<<x<<endl;
}

03函数重载与重载解析

函数重载:使用相同的函数名定义多个函数,每个函数具有不同的参数列表

   -不能基于不同的返回类型进行重载 ----因为返回值可以不接受 故编译器不知道调用那函数

        int fun(int x){..}   double fun(int x){....}   error

int fun()
{
    return x+1;
}
double fun(double x)
{
    return x+1;
}

fun(1)
fun(2.1)

编译器如何选择正确的版本完成函数调用?

名称查找-----xxx------重载解析---------xxx----------xxx------------xxxx-------------xxx

本例重点介绍名称查找和重载解析

名称查找:

 1.限定查找

        -通常指定域   全局域 还是命名空间域     ::fun();      MyNS::fun();

2.非限定查找

        -会进行域的逐级查找  ----名称隐藏

3.查找通常只会在已经申明的名称集合中进行   从上到下按顺序执行查找

void fun(int x) {.....}
void g()
{ 
    fun(3);
}
void fun (double x)
{....}

int main()
{
    g(); 执行第一个fun  因为调用个g() fun(3)  第二个fun没有生成  除非在g()前声明 
}
    

重载解析:在名称查找后  进一步选择合适的调用函数

---过滤不能被调用的版本 

        参数个数不对,   无法将实参转化为实参,  实参不满足形参的限制条件

---在剩余版本中查找与调用表达式最匹配的版本  匹配级别越低越好(有例外情况)

● 级别 1 :完美匹配 或 平凡转换(比如加一个 const )
● 级别 2 : promotion 或 promotion 加平凡转换
● 级别 3 :标准转换 或 标准转换加平凡转换
● 级别 4* :自定义转换 或 自定义转换加平凡转换 或 自定义转换加标准转换
● 级别 5* :形参为省略号的版本
● 函数包含多个形参时,所选函数的所有形参的匹配级别都要优于或等于其它

void fun(int x){..........}
void fun(double x){........}

fun(3);   调用第一个

void fun(int &x){........}
void fun(const int &x){.........}
int x;
fun(x);  调用第一个fun()  x为左值可修改
fun(3);    调用第二个fun() 因为第一个fun 被过滤  int &x不能绑定常量

void fun(int x,int y){....}
void fun(int x,double y){..........}
fun(1,1.0)        调用第二个fun                      int x          int y
                                        级别:      1 完美匹配      3 标准转换
                                                   1 完美匹配       1 完美匹配

04函数其他

递归函数

递归函数:在函数中调用自己  ----典型案例二分查找(有序表)

int binary_search(int *arr,int p,int q,int ele) {
    int mid = 0;
    //如果[p,q] 不存在,返回 -1
    if (p > q) {
        return -1;
    }
    // 找到中间元素所在的位置
    mid = p + (q - p) / 2;
    //递归的出口
    if (ele == arr[mid]) {
        return mid;
    }
    //比较 ele 和 arr[mid] 的值,缩小 ele 可能存在的区域
    if (ele < arr[mid]) {
        //新的搜索区域为 [p,mid-1]
        return binary_search(arr, p, mid - 1, ele);
    }
    else {
        //新的搜索区域为 [mid+1,q]
        return binary_search(arr, mid + 1, q, ele);
    }
}

二分查找算法(折半查找算法)二分查找算法又称折半查找算法,是在分治算法基础上实现的查找算法。本文将详细讲解二分查找算法的实现思路,还会给出二分查找算法对应的C/C++、Java、Python实现代码。http://c.biancheng.net/algorithm/binary-search.html

内联函数

避免函数调用的栈帧结构,本质是一种优化方法.用inline放在函数开头,且函数必须有定义,函数从程序一次定义变为翻译单元一次定义

#include<iostream>
void fun()
{
    cout<<"Hello"<<endl;
}
int main()
{
    fun(); 调用fun()本质  是将fun()非简单替换为cout<<"Hello"<<endl;
}

main.cpp
#include<iostream>
#include"header.h"
int main()
{
    fun();   两次调用fun  出现重复定义 加入inline  将fun变为翻译单元一次定义即可解决报错
}

header.h
#include<iostream>
inline void fun()  加入inline定义一个内联函数  一定要定义 不能只声明
{
    cout<<"Hello<<endl;
}

source.cpp
#include<iostream>
void fun2()
{
    fun();
}

constexpr C++11  14做了修改

C++11 constexpr:验证是否为常量表达式(长篇神文)constexpr 是 C++ 11 标准新引入的关键字,不过在讲解其具体用法和功能之前,读者需要先搞清楚 C++ 常量表达式的含义。 所谓常量表达式,指的就是由多个(1)常量组成的表达式。换句话http://c.biancheng.net/view/7781.html

constexpr int x=0;   定义一个编译器的常量

constexpr int x=3;
constexpr int fun(int x)    修饰函数--常量表达式函数  函数既能在编译器也能在运行期执行
{
    return x+1;
}
int main()  
{
    constexpr int x=fun(3);
    returnx;
}                     

获得在编译阶段计算出结果的能力,并不代表 constexpr 修饰的表达式一定会在程序编译阶段被执行,具体的计算时机还是编译器说了算

consteval   C++20consteval 说明符 (C++20 起) - cppreference.comhttps://zh.cppreference.com/w/cpp/language/consteval

consteval:只能在编译期执行

consteval int fun(int x)
{
    return x+1;
}
int main()
{
    constexpr int x=3;  若无constexpr则报错 因为consteval只能在编译器运行
    int y=fun(x);
}

函数指针

函数类型与函数指针

函数类型
int fun(int x)
{
    return x+1;
}
using k=int(int);
k fun; 相当于函数声明
int(int) fun ;  error

函数指针
int inc(int x)
{
    return x+1;
}
using K=int(int);
int Twice(K*fun,int x)   高阶函数
{
    int temp=(*fun)(x);
    return temp*2;
}
int main()
{
    K*fun=&inc;
    cout<<(*fun)(100)<<endl;   101
    cout<<(inc(100))<<endl;  101
    cout<<Twice(&inc,100)<<endl;  202
}

auto fun=inc;    函数数组不能复制  默认转化为指针 fun为函数指针类型  int(*)(int)
using K=int(int);
K* fun;   等价于int(*fun)(int)   函数指针

void demo(K input) /(K*input)
{......}
demo(inc);  传入指针  接受也是指针
demo(&inc);

函数指针与重载

将函数指针作为函数参数

将函数指针作函数返回值

函数指针与重载
void fun(int)
{.......}
int main()
{
    auto x=fun();  x为函数指针类型void(*)(int)
}

void fun(int){...}
void fun(int ,int){...}
int main()
{
   using K=void(int);
    K* x=fun();  显示写出x的类型
}

做函数返回值
void inc(int x)
{
    return x+1;
}
void dec(int x)
{
    return x-1;
}
auto fun(bool input)
{
    if(input)return inc;  
    else return dec;    inc  dec  做返回值均转化为函数指针
}
int main()
{
    cout<<(*fun(true))(100)<<endl;   101
}

持续更新...............................

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值