• c++函数参数和返回值


    c++函数参数和返回值

    c++一直以来是一个关注效率的代码,这样关于函数的参数传递和返回值的接收,是重中之重。下文提供了一些个人的见解。

    函数存储位置

    函数参数在编译期展开,目前各平台的编译期均有不同。

    名称 存储位置
    函数名称和逻辑 代码段存储
    函数参数和返回值 栈中或者寄存器(64位会有6个寄存器使用)
    new malloc 的变量

    函数参数入栈顺序

    微软有几种编译期属性,用来定义函数参数的顺序和堆栈。

    关键字 堆栈清理 参数传递
    __cdecl 调用方 在堆栈上按相反顺序推送参数(从右到左)
    __clrcall 不适用 按顺序将参数加载到 CLR 表达式堆栈上(从左到右)。
    __stdcall 被调用方 在堆栈上按相反顺序推送参数(从右到左)
    __fastcall 被调用方 存储在寄存器中,然后在堆栈上推送
    __thiscall 被调用方 在堆栈上推送;存储在 ECX 中的 this 指针
    __vectorcall 被调用方 存储在寄存器中,然后按相反顺序在堆栈上推送(从右到左)

    所以直接在函数参数上,调用表达式和函数来回去值的话,非常危险

    初始化列表

    class Init1
    {
    public:
    
        void Print()
        {
            std::cout << a << std::endl;
            std::cout << b << std::endl;
            std::cout << c << std::endl;
        }
    
        int c, a, b;
    };
    

    A这个类,可以通过 A a{1,2,3}; 来初始化对象。
    看着很美好,但是有几个问题需要注意。
    参数是的入栈顺序是跟着类的属性的顺序一致, 当前是 c, a, b;

    int i = 0;
    Init1 a = {i++, i++, i++};
    a.Print();
    

    当我如此调用的时候,得到的返回值是 1 2 0
    i++的执行顺序是从左到右,跟函数调用顺序无关。 另外不能有 构造函数

    	class Init1
    	{
    	public:
    		Init1(int ia, int ib, int ic)
    		{
    			std::cout << "construct" << std::endl;
    			a = ia;
    			b = ib;
    			c = ic;
    		}
    		Init1(const Init1& other)
    		{
    			std::cout << "copy " << std::endl;
    			a = other.a;
    			b = other.b;
    			c = other.c;
    		}
    
    		void Print()
    		{
    			std::cout << a << std::endl;
    			std::cout << b << std::endl;
    			std::cout << c << std::endl;
    		}
    
    		int c, a, b;
    	};
    

    当我添加了构造函数的时候。 用下面代码测试。会得到两种结果

    void Test_InitilizeList()
    {
    	int i = 0;
    	//Init1 a = { i++, i++, i++ }; // 0 1 2 
    	Init1 a(i++, i++, i++); // 2 1 0 
    	a.Print();
    }
    

    函数的返回值

    函数返回值的声明周期在函数体内。

    用参数引用来返回

    class Result
    {
    public:
    int result;
    };
    void GetResult(Result& result) ...
    

    优点:

    • 效率最高,因为返回值的对象在函数体外构造,可以一直套用, 可以一处构造,一直使用。
    • 安全,可以定义对象,并不用new或者malloc, 没有野指针困扰。
      缺点:
    • 代码可读性低,不够优美
    • 无法返回nullptr. 一般在 Result 中定义一个; 用来表示一个空对象。
    • 容易赋值到一个临时对象中,当调用GetResult({1}) 会赋值到一个 临时的 Result 对象中,拿不到返回值。正常来说也不会这样做。

    返回一个参数指针

    class Result
    {
    public:
    int result;
    };
    Result* GetResult() ...
    

    优点:

    • 简洁明了
    • 参数传递快速
      缺点:
    • 指针如果在 函数内 static 需要考虑多线程。 如果是 new 出来的,多次调用效率不高
    • 指针无法重复使用,(可以用 std::share_ptr 增加对象池来解决问题。但会引入新的复杂度。)
    • 需要考虑释放的问题

    返回一个对象

    class Result
    {
    public:
    int result;
    };
    Result GetResult() ...
    

    优点:

    • 没有内存泄露的风险
    • 简洁明了
      缺点:
    • 但有个别编译期优化选项问题,会导致一次构造两次拷贝, 第一次是函数体内对象向返回值拷贝,第二次是 返回值拷贝给外面接收参数的。
    • 开启编译期优化选项,并且是 在 return Result 的时候构造返回对象,才能优化。

    总结

    一般如果是 简单结构体,用 返回一个临时对象的方式解决。
    如果使用 返回一个参数指针,一般改成返回一个id,用一个manager来管理内存机制。或者 共享内存,内存池来解决内存泄露后续的问题
    用 参数引用来返回的话,一般会这么定义 int GetResult(Result& result) 函数返回值,用来返回状态码,真正的数据,放到 result 中。

    函数的几种变体

    inline 函数

    • inline 函数是内联函数,是编译期优化的一种手段,一般是直接展开到调用者代码里,减少函数堆栈的开销。
    • inline 标识只是建议,并不是一定开启内联。
    • 函数比较复杂或者递归有可能编译期不展开。
    • dll 导出的时候,可以不用加导出标识,会直接导出到目标处。
    • inline 在msvc的平台,只要实现头文件中,加不加内联是一样的. (警告顶级调到最高/Wall, 不加inline标识的函数会提示,未使用的内联函数将被删除。)
    • inline 函数比全局函数更快,但是全局函数无法定义在头文件中(会报多重定义函数。)所以一般用class 包一层 static inline 函数,用来写工具类。

    函数对象

    class A {
    public :
        int value;  
        int operator() (int val) {
            return value + val;
        }
    }
    

    上述代码是一个函数对象,重载operator()得到一个函数对象。
    int a = A{10}(1) 会返回11, 显示构造了一个A{value=10}的对象,然后调用重载函数operator(), 返回 10 + 1 = 11
    上述代码因为是在头文件实现的,所以编译期会自动把operator()函数当成inline函数,执行效率很高。

    lambda 函数

    lambda 其实就是一个函数对象,会在编译期展开成一个函数对象体。

  • 相关阅读:
    【Python】Python中的lambda表达式
    Linux电脑如何下载QGIS?
    MySQL 日志管理
    Linux cp 命令使用介绍
    08/13摸鱼周
    云计算 - 3 - 使用MapReduce处理数据
    合并多个.a库为一个.a库
    Easyui DataGrid combobox联动下拉框内容
    一种高精度紧耦合的双目VI-SLAM算法
    Python&SQL应用随笔4——PySpark创建SQL临时表
  • 原文地址:https://www.cnblogs.com/zijian-yang/p/17415864.html