精华内容
下载资源
问答
  • 1.函数接口说明 在C++中指定函数接口时,一些必须要解决的问题有: (1) 运算符函数还是非运算符函数? (2) 自由运算符还是成员运算符? (3) 虚函数还是非虚函数? (4) 纯虚成员函数还是非纯虚成员函数? (5) ...

    在C++中指定函数接口时,一些必须要解决的问题有:

    (1) 运算符函数还是非运算符函数?
    (2) 自由运算符还是成员运算符?
    (3) 虚函数还是非虚函数?
    (4) 纯虚成员函数还是非纯虚成员函数?
    (5) 静态成员函数还是非静态成员函数?
    (6) 常量成员函数还是非常量成员函数?
    (7) public、protected 还是 private 成员函数?
    (8) 通过值、引用还是指针返回?
    (9) 返回常量还是非常量?
    (10) 参数是可选的还是必需的?
    (11) 通过值、引用还是指针传递参数?
    (12) 将参数作为常量传递还是非常量传递?

    有俩个组织问题,尽管不属逻辑接口的一部分,但是也必需解决:

    (13) 友元函数还是非友元函数?
    (14) 内联函数还是非内联函数?

    (1) 运算符函数还是非运算符函数

    除了编译器生成的运算符(例如赋值) 之外,将一个函数函数设计成一个运算符函数的唯一理由是方便客户端的标记。

    一个成员函数调用一个运算符产生的函数调用解析,和在文件作用域中函数调用的解析是一样的。

    1) 可读性(超过易用性) 应该是运算符重载的主要原因。
    2) 一个重载运算符的语义对于用户来应该是自然、明显和直观的。
    3) 预定义C++ 运算符之后,模拟用户自定义运算符的语法性质,可以避免意外并且使它们的使用具有更高的可预测性。

    在C++中,每个表达式都有一个值。有俩种基本类型的值,分别称为左值 (lvalue) 和右值 (rvalue)。左值是可以获得其地址的值,如果一个左值可以在赋值语句的“左边”,就被认为是一个可修改的左值,否则被认为是一个不可修改的左值。

    右值不能被赋值,也不能获得它的地址。最简单的左值表达式是变量标识符,除非这个变量被声明为常量,否则该变量就是一个可修改的左值。

    某些运算符,例如,赋值运算符 (=) 和它的变化形式(+=、-=、*=、/=、^=、&=、| =、~=、%=、>>=、<<=)、前置增量(++X)和前置减量,使用基本类型时都返回可修改的左值,这些运算符总是返回一个指向修改后参数的可写引用。例如,对于基本类型double(如果作为一个C++类实现),这些运算符假设的定义如下代码所示:

    class double{
        //...
    public:
        double(){}
        double(int);
        doubel(const double&);
        ~double(){}
    
        double& operator=(const double& d);
        double& operator+=(const double& d);
        double& operator-=(const double& d);
        double& operator*=(const double& d);
        double& operator/=(const double& d);
    
        double& operator++();  //pre-increment ++x
        double& operator++();  //pre-decrement --x
        double operator++(int); //post-increment x++
        double operator--(int); //post-decrement x--
    
        double *operator&();  //unary address operator
        const double *operator&() const;  //
    };
    
    double operator+(const double& d);
    double operator-(const double& d);
    
    int operator!(const double& d);
    
    int operator&&(const double& left, const double& right);
    int operator||(const double& left, const double& right);
    
    double operator+(const double& left, const double& right);
    double operator-(const double& left, const double& right);
    double operator*(const double& left, const double& right);
    double operator/(const double& left, const double& right);
    
    double operator==(const double& left, const double& right);
    double operator!=(const double& left, const double& right);
    double operator<(const double& left, const double& right);
    double operator<=(const double& left, const double& right);
    double operator>(const double& left, const double& right);
    double operator>=(const double& left, const double& right);

    由于没有合适的左值返回,所以上述代码所示的其他运算符返回一个右值。

    至于对称的二元运算符(例如,+和*),所返回的值既不是左边的参数也不是右边的参数,而是一个派生于这俩个值的新值,因此必须通过值来返回。

    等式(==、!=)和关系运算符(<,<=,>,>=)总是返回一个int类型的右值,不是0就是1,显然,没有输入参数适合在这里返回。

    后置递增和后置递减运算符是比较特别,只有它们可以修改对象却没有返回适当的左值:

    double double::operator++(int)
    {
        double tmp= *this;
        ++ *this;
        return tmp;
    }
    
    double double::operator--(int)
    {
        double tmp= *this;
        -- *this;
        return tmp;
    }

    这里写图片描述

    上图总结了应用于基本类型时大多数C++运算符的声明。值得注意的是,没有修改其参数的一元运算不是基本成员。例如,对一个诸如 “ostream”的用户定义类型,一元运算符 “!”,即使没有给这种类型定义 “!” 运算符:

    #include"iostream.h"
    void g(ostream &out)
    {
        if(!out){
            cerr<<"output stream is bad"<<endl;
            return;
        }
        //...
    }

    代码工作正常,因为ostream知道如何将自己隐式地转换为定义了“!”运算符的基本类型(void *) (这里有点搞不明白???希望有大神可以指点下)

    (3) 虚函数还是非虚函数

    动态绑定能使通过一个基类访问的成员函数由实际对象的子类型来决定,一个函数必须声明为virtual才能被动态绑定。

    首先简单介绍下动态绑定和静态绑定:

    =================================================================================================
    静态绑定:编译时绑定,通过对象调用
    动态绑定:运行时绑定,通过地址实现

    C++的多态性:
    1) 静态多态性:函数多态性—函数重载、模板多态性—C++模板(类模板、函数模板)
    2) 动态多态性:虚函数(只有用地址才能实现动态多态性)

    只有采用 “指针->函数()”“引用变量.函数()” 的方式调用C++类中的虚函数才会执行动态绑定。对于C++ 中的非虚函数,因为其不具备动态绑定的特征,所以不管采用什么样的方式,都不会执行动态绑定。

    C++语言成员函数的调用和绑定方式总结:

    这里写图片描述

    注:被引用对象所属类指针或引用指向的对象 的实际类型
    引用变量所属类、指针变量所属类 是定义 引用变量、指针变量的类型。

    具体代码如下:

    #include<iostream>
    class CBase
    {
    public:
        virtual int func() const //虚函数
        {
            cout<<"CBase function!"return 100;
        }
    };
    class CDerive:public CBase
    {
    public:
        int func() const
        {
            cout<<"Derive function!";
            return 200;
        }   
    };
    void main()
    {
        CDerive obj1;
        CBase* p1=&obj1;
        CBase& p2=obj1;
        CBase obj2;
    
    obj1.func(); //静态绑定:调用对象本身(派生类CDerive对象)的func函数
    p1->func(); //动态绑定:调用被引用对象所属类(派生类CDerive) 的func函数
    p2.func(); //动态绑定,调用被引用对象所属类(派生类CDerive)的func函数
    obj2.func(); //静态绑定,调用对象本身(基类CBase对象)的函数
    }

    运行结果为:

    Derive function!
    Derive function!
    Derive function!
    CBase function!

    虚函数的作用:
    基于向上的类型转换,基类通过虚函数可以对多个子类相似的功能实现统一管理。
    正如上面代码,CBase 为基类,CDerive为子类,且virtual 只需要在基类中标识一次,子类无需重复标识。

    CDerive obj1;  
    CBase* p1=&obj1;
    CBase& p2=obj1;
    CBase obj2;

    代码中,obj1对象向上转换为 CBase 类型,输出 “Derive function!”

    因此:
    1.尽管在顶层函数的定义是以基类CBase 作为其参数,但却能接受基类CBase的子类CDerive作为其参数。事实上,这是自动向上类型转换。
    2.虽然子类转换成了它的父类型,但却课正确调用子类而不属于父类的成员函数,这就是虚函数的功劳。

    =================================================================================================

    1) 虚函数实现行为的变化;数据成员实现值的变化。
    更一般的,虚函数用于描述跨越派生自同一基类类型的行为变化,但是数据成员不必借助于继承就足以描述值的变化。

    隐藏(hide):在一个基类或者一个文件作用域中,一个成员函数隐藏声明为相同名称的函数。
    重载:具有相同名称的不同函数在同一作用域中的声明。

    class CDerive:public CBase
    {
    public:
        int func() const   
        {
            cout<<"Derive function!";
            return 200;
        }   
        int func(int x,int y) const  //重载函数
        {
        //... 
        }
    };

    覆盖:当一个基类中声明为虚函数的具有相同函数接口的函数来声明一个派生类中的一个成员函数时,我们说该成员函数覆盖了这个基类函数。

    class CBase
    {
    public:
        virtual int func() const //虚函数
        {
            cout<<"CBase function!"return 100;
        }
    };
    class CDerive:public CBase
    {
    public:
        int func() const   //覆盖了基类CBase中的int func() const 函数
        {
            cout<<"Derive function!";
            return 200;
        }   
    };
    

    重定义:在一个函数的默认定义被函数的另一个定义彻底代替。

    2) 避免在一个派生类中隐藏一个基类函数

    不要在派生类中隐藏任何基类函数的定义,我们一定不要为一个派生类中的非虚函数提供一个新的定义,因为这会使该函数对任何指针类型或者引用类型敏感。

    #include<iostream>
    class CBase
    {
    public:
         int func() const 
        {
            cout<<"CBase function!"return 100;
        }
    };
    class CDerive:public CBase
    {
    public:
        int func() const   //不可取,在派生类中的非函数提供一个新的定义
        {
            cout<<"Derive function!";
            return 200;
        }   
    };

    (4) 纯虚成员函数还是非纯虚成员函数?

    通常在实现一个类族接口的时候使用纯虚函数,接口一般就是一个抽象类(这里为啥?接下来解释)

    为啥需要纯虚数?
    1.有时候在基类中将某一函数成员定义为虚函数,并不是基类本身的要求,而是考虑到派生类的需要,在基类中预留一个函数名,具体功能留给派生类根据需要去定义。 (需要)

    2.纯虚函数其实就是声明一个函数,在派生类中再定义它。也就是说纯虚数函数只有函数名字而不具备函数的功能,不能被调用。

    一般情况下,纯虚函数是用来定义抽象基类的时候来使用的,所谓抽象基类就是一种不用定义对象,而只作为一种基本类型用作继承的抽象类,而常常用它来作为基类,所以叫抽象基类。凡是包含纯虚函数的类都是抽象类,因为纯虚函数时不能被调用的,包含纯虚函数的类是无法建立对象的。

    抽象类的作用是作为一个类族的共同基类,或者为一个类族提供一个公众接口。例如,动物作为一个基类可以派生出老虎、狮子等,但动物本省生成对象明显不合常理。动物只是一个抽象概念,动物都有动作、大小等属性。

    class Point
    {
    public:
      virtual float area( );//虚函数
      virtual float volume();//虚函数 
      virtual void shapeName()=0; //纯虚函数
    }

    使用纯虚数要注意:
    1. 纯虚数没有函数体;
    2. 最后面的 ”=0“ 并不是表示函数值返回值为0,它只是形式上的作用,告诉编译系统这是纯虚函数。
    3. 这是一个声明语句,最后以分号结束。

    #include <iostream>
    using namespace std;
    struct Base{
        virtual void f()=0;   //纯虚函数,因为没有函数体
        virtual ~Base();
    };
    
    Base::~Base() {}
    
    struct Partial:Base{
        virtual void f();   //虚函数,有函数体
        ~Partial();
    };
    void Partial::f()
    {
        cout<<"Partial::f"<<endl;
    }
    
    Partial::~Partial(){}
    
    struct Derived:Partial{
        Derived(){}
        void f();
        ~Derived();
    };
    void Derived::f()
    {
        cout<<"Derived::f"<<endl;
        Partial::f();
    }
    Derived::~Derived(){}
    
    int main()
    {
        Derived obj1;
        Base *b=&obj1;
        b->f();
    }

    output:

    Derived::f
    Partial::f

    (5) 静态还是非静态成员函数?

    使一个函数成为一个类的静态成员函数最直接的方法是,让它不依赖于对象的任何特定实例:

    //math.h
    class my_Wiget {
        static int d_instanceCount;  //由于为private变量,因此不能被其他组件所访问
    public:
        static int instanceCount()
        {
            return d_instanceCount;
        }
    }
    //math.cpp
    
    int my_Wiget::d_instanceCount=6;

    为什么要用静态成员函数:

    //math.h
    #ifndef INCLUDE_MATH
    #define INCLUDE_MATH
    
    class Test{
    private:
       static int d_test;
    public:
       Test(){};
       int d_get_size(){return d_test;}
       static int get_size(){return d_test;}
    };
    #endif
    //math.cpp
    #include "math.h"
    int Test::d_test=2;
    //main.cpp
    #include "math.h"
    #include <iostream>
    int main()
    {
        cout<<Test::get_size()<<endl;   //直接调用静态成员函数
        Test d_get_size;    //成员函数需要先声明实例,再调用成员函数
        cout<<d_get_size.d_get_size()<<endl; 
    }

    output:

    2
    2

    (6) 常量成员函数还是非常量成员函数

    一个常量成员函数允许修改并返回一个指向内存的可写引用,该引用由一个对象拥有和管理。

    class ex_String{
        char *d_str_p;
    public:
        // ...
        makeNull()  {d_str_p=0;}  //physically non-const
        makeEmpty() const{d_str_p[0]=0;} //physically const
    
        char *&getRepRef() {return d_str_p;}
        char * getRep() const {return d_str_p;} //physically const,可写
    }

    从一个常量成员函数返回可写访问到一个对象内部的表示,会使编译器在确保一个常量不被修改的能力丧失掉。为了避免”从一个常量成员函数返回一个非常量对象会破坏一个系统的常量校正性。“,要使用const 校正。

    class te_Node{
    public:
        //MANIPULATORS
        void setValue(double v);
    
       //ACCESSORS
       const char *name() const;
       te_Node *parent() const; 返回的指针可写;
       te_Node *child1() const; 返回的指针,可写;
       te_Node *child2() const; 
    }

    const 校正后:

    class te_Node{
    public:
        //MANIPULATORS
        void setValue(double v);
        te_Node *parent();  //可写
        te_Node *child1();
        te_Node *child2(); 
    
       //ACCESSORS
       const char *name() const;
       const te_Node *parent() const; 返回的指针不可写;
       const te_Node *child1() const; 返回的指针不可写;
       const te_Node *child2() const; 
    }

    (7) 公共的、受保护的还是私有的成员函数

    专为一般用户直接使用的成员函数必须为公共的(public)。像等式运算符或关系运算符这样的自由运算符可以按照基本成员函数来实现,若不是公共的,那么自由运算符必须声明为这个类的友元。

    类的一个特征就是封装,public和private作用就是实现这一目的:
    代码(类外)可以访问public成员而不能访问private成员,private成员只能由类成员(类内)和友元访问。

    类的一个特征就是继承,protected的作用就是实现这一目的的:
    protected成员可以被派生类对象访问,类成员可以访问(类内),不能被用户代码(类外)访问。

    #include<iostream>
    #include<assert.h>
    using namespace std;
    class A{
    public:
      int a;
      A(){
        a1 = 1;
        a2 = 2;
        a3 = 3;
        a = 4;
      }
      void fun(){
        cout << a << endl;    //正确
        cout << a1 << endl;   //正确
        cout << a2 << endl;   //正确,类内访问
        cout << a3 << endl;   //正确,类内访问
      }
    public:
      int a1;
    protected:
      int a2;
    private:
      int a3;
    };
    int main(){
      A itema;
      itema.a = 10;    //正确
      itema.a1 = 20;    //正确
      itema.a2 = 30;    //错误,类外不能访问protected成员
      itema.a3 = 40;    //错误,类外不能访问private成员
      system("pause");
      return 0;
    }

    继承中的特点:
    对于继承,又有public、protected、private三种继承方式,它们相应地改变了基类成员的属性:

    a. public 继承: 基类public成员、protected成员、private成员的访问属性在派生类中分别变成:public、protected、private

    b. protected 继承: 基类public成员、protected成员、private成员的访问属性在派生类中分别变成:protected、protected、private

    c. private 继承:基类public成员、protected成员、private成员的访问属性在派生类中分别变成:private、private、private

    ++++++++++++++++++++++++++++++++++++++++++++++++++++
    但是无论哪种继承方式,上面俩点都没有改变:
    1.private成员只能被本类成员(类内)和友元类成员访问,不能被派生类访问。
    2.protected 成员可以被派生类访问。
    ++++++++++++++++++++++++++++++++++++++++++++++++++++

    a. Public继承:

    #include<iostream>
    using namespace std;
    class A{
    public:
        int a;
        A(){
        a1=1;
        a2=2;
        a3=3;
        a=4;
        }
        void fun(){
        cout<<a<<endl; //正确,访问类内public成员变量
        cout<<a1<<endl; //正确,访问类内public成员变量
        cout<<a2<<endl; //正确,访问类内protected成员变量
        cout<<a3<<endl; //正确,访问类内private成员变量
        }
    publicint a1;
    protected:
        int a2;
    private:
        int a3;
    };
    
    class B:public A
    {
    public:
        int a;
        B(int i)
        {A();
         a=i;
        }
        void fun(){
        cout<<a<<endl;  //正确,类内public成员
        cout<<a1<<endl;  //正确,访问基类public成员
        cout<<a2<<endl; //正确,访问基类protected成员
        cout<<a3<<endl;  //错误,不能访问基类private成员
        }
    };
    
    int main()
    {
        B b(10);
        cout<<b.a<<endl; //正确,静态绑定,访问类内public成员
        cout<<b.a1<<endl; //正确,访问基类public成员
        cout<<b.a2<<endl; //错误,不能访问基类protected成员
        cout<<b.a3<<endl; //错误,不能访问基类private成员
        return 0;
    }

    b. protected继承:

    #include<iostream>
    using namespace std;
    class A{
    public:
        int a;
        A(){
        a1=1;
        a2=2;
        a3=3;
        a=4;
        }
        void fun(){
        cout<<a<<endl; //正确,访问类内public成员变量
        cout<<a1<<endl; //正确,访问类内public成员变量
        cout<<a2<<endl; //正确,访问类内protected成员变量
        cout<<a3<<endl; //正确,访问类内private成员变量
        }
    publicint a1;
    protected:
        int a2;
    private:
        int a3;
    };
    
    class B:protected A
    {
    public:
        int a;
        B(int i)
        {A();
         a=i;
        }
        void fun(){
        cout<<a<<endl;  //正确,类内public成员
        cout<<a1<<endl;  //正确,基类public成员在继承后变为protected成员,但是依旧可以访问
        cout<<a2<<endl; //正确,基类protected成员在继承后仍为protected成员,依旧可以访问
        cout<<a3<<endl;  //错误,不能访问基类private成员
        }
    };
    
    int main()
    {
        B b(10);
        cout<<b.a<<endl; //正确,静态绑定,访问类内public成员
        cout<<b.a1<<endl; //错误,protected继承后,基类的public成员在派生类中变为protected权限,因此不能访问
        cout<<b.a2<<endl; //错误,不能访问基类protected成员
        cout<<b.a3<<endl; //错误,不能访问基类private成员
        return 0;
    }

    c. private继承:

    #include<iostream>
    using namespace std;
    class A{
    public:
        int a;
        A(){
        a1=1;
        a2=2;
        a3=3;
        a=4;
        }
        void fun(){
        cout<<a<<endl; //正确,访问类内public成员变量
        cout<<a1<<endl; //正确,访问类内public成员变量
        cout<<a2<<endl; //正确,访问类内protected成员变量
        cout<<a3<<endl; //正确,访问类内private成员变量
        }
    publicint a1;
    protected:
        int a2;
    private:
        int a3;
    };
    
    class B:private A
    {
    public:
        int a;
        B(int i)
        {A();
         a=i;
        }
        void fun(){
        cout<<a<<endl;  //正确,类内public成员
        cout<<a1<<endl;  //正确,基类public成员在private继承后变为private成员,可以被派生类访问
        cout<<a2<<endl; //正确,基类protected成员在private继承后变成private成员,可以被派生类访问
        cout<<a3<<endl;  //错误,不能访问基类private成员
        }
    };
    
    int main()
    {
        B b(10);
        cout<<b.a<<endl; //正确,静态绑定,访问类内public成员
        cout<<b.a1<<endl; //错误,protected继承后,基类的public成员在派生类中变为protected权限,因此不能访问
        cout<<b.a2<<endl; //错误,不能访问基类protected成员
        cout<<b.a3<<endl; //错误,不能访问基类private成员
        return 0;
    }

    所以派生类包含了基类所有成员以及新增的成员,同名的成员被隐藏起来(就是说当派生类与基类有相同名称的函数的时候,会将基类的该函数隐藏起来),调用的时候只会调用派生类中的成员。

    (8) 值返回、引用返回还是指针返回

    1.首先说函数内的对象的保留和处理——当函数结束的时候,所有除了堆中的对象(不考虑传入了什么样的参数的前提下)之外的函数成员,全部会被处理掉,无法保留。

    ======================================================================================
    如何定义一个只能在堆上(栈上)生成对象的类?
    在C++ 中,类的对象建立分俩种,一种是静态建立,如 A a; 另一种是动态建立,如 A* ptr= new A;

    静态建立一个类对象,是由编译器为对象在栈空间中分配内存,是通过直接移动栈顶指针,挪出适当的空间,然后在这片内存空间上调用构造函数形成一个栈对象。

    动态建立类对象,使用new运算符将对象建立在堆空间中。这个过程分倆步:1.执行operator new() 函数,在堆空间中搜索合适的内存并进行分配;2.调用构造函数构造对象,初始化这片内存空间。

    只能在堆上建立对象,就是不能在栈上建立类对象,即不能直接调用类的构造函数,实现办法为将析构函数设为私有,类对象就无法建立在栈上了。

    只能在栈上建立对象,就是不能在堆上建立类对象,将operator new() 设为私有即可。

    详情见:https://www.cnblogs.com/vincently/p/4838283.html

    ======================================================================================

    2.其次,在返回的过程中,其实无论是引用,指针还是值,只要是跟在return后面的,都会以复制的方式来返回。但在这里,复制引用和指针不代表复制他们所引用、所指向的对象(或内存对象)。因此,复制它们的只是复制了一个入口,这也是这种复制更节省资源的原因。

    3.最后,所有因为在函数的return后面而被作为返回复制出来的东西(包括值,引用和指针),都会在函数被调用的那一行执行结束后,被清理掉(唯有一种情况例外,就是被复制出来的是值)。而被清理掉之前,要想完成传递,必须把它们赋予另一个变量或引用或指针。但是赋予变量或者引用或指针,却会导致不同的结果,分析如下:

    第一种情况:函数如果返回的是值,那么在函数调用时,返回的值会被从函数中复制出来使用。当函数结束时,函数中的对应对象会被析构或结束,但复制出来的依旧存在。

    第二种情况:函数如果返回的是引用,那么在函数调用时,引用的对象不被复制,只是引用本身被复制了。于是,当函数的对象被析构时,引用就会为空,而指针则虽然找到了地址,但却因为对象的值被处理而出现失效的问题。

    第三种情况:函数如果返回的是指针,那么在函数调用时,指针指向的对象不会被复制,只复制了指针。若指针的对象在堆中,那么可以继续使用指针指向堆中的对象,因为这里对象的地址得到了继承。

    (9) 返回常量还是非常量

    为了消除不必要的限制,我们尽量采用常量操作数并返回非常量的结果。

    (10) 可选参数还是必选参数

    只有一个函数体通常比有若干个重载版本更容易维护。实际上,在大多数情况下,使用内联函数创建允许可选参数位于参数表中间位置的重载版本是足够容易的。

    使用重载函数

    geom_Point{
        int d_x;
        int d_y;
    private:
        void init(x,y);
    public:
        geom_Point();
        geom_Point(int x, int y);
        //...
    };
    
    geom_Point operator+(const Point&,const Point&);
    
    inline void geom_Point::init(int x, int y)
    {
        d_x=x;
        d_y=y;
    }
    
    inline geom_Point::geom_Point()
    {
        int(0,0);
    }
    
    inline geom_Point::geom_Point(int x, int y)
    {
        init(x,y);
    }

    使用默认参数:

    geom_Point{
        int d_x;
        int d_y;
    
    public:
        geom_Point(int x=0, int y=0);
        //...
    };
    
    geom_Point operator+(const Point&,const Point&);
    
    inline geom_Point::geom_Point(int x, int y)
    :d_x(x),d_y(y)
    {
    }

    (11) 通过值、引用还是指针来传递参数

    1) 千万不要通过值来传递用户自定义类型(例如类、结构体或联合体)给一个函数。

    这里不是通过值传递一个用户自定义类型,而是由常量引用传递这个用户自定义类型。在没有全局变量时,俩者的语义基本是相同的,但后者运行时性能更加。枚举和所有基本类型通过值传递最为有效。

    void f(my_Object& result, ... ); //通过引用
    void f(my_Object *result, ... ); //通过指针

    其中,引用不能为空,而指针可以为空。

    值传递: 形参是实参的拷贝,改变形参的值不会影响到外部实参的值。值传递是单向的(实参->形参)。
    指针传递 :形参为指向实参地址的指针,当形参的指向操作时,就相当于对实参本身进行惭怍。
    引用传递: 形参相当于是实参的“别名”,对形参的操作实际就是对实参的操作,在引用传递过程中,被调函数的形式虽然也作为局部变量在栈中开辟了内存空间,但是这时存放是由主调函数放进来的实参变量的地址。被调函数对形参的任何操作都被处理成间接寻址,即通过栈存放的地址访问主调函数中的实参变量。

    #include<iostream>
    using namespace std;
    //值传递
     void change1(int n){
        cout<<"值传递--函数操作地址"<<&n<<endl;         //显示的是拷贝的地址而不是源地址 
        n++;
    }
    
    //引用传递
    void change2(int & n){
        cout<<"引用传递--函数操作地址"<<&n<<endl; 
        n++;
    }
     //指针传递
    void change3(int *n){
         cout<<"指针传递--函数操作地址 "<<n<<endl; 
        *n=*n+1;
     } 
    int     main(){
        int n=10;
        cout<<"实参的地址"<<&n<<endl;
        change1(n);
        cout<<"after change1() n="<<n<<endl;
        change2(n);  //传入一个引用,在栈开辟内存,存放实参的地址
        cout<<"after change2() n="<<n<<endl;
        change3(&n);
        cout<<"after change3() n="<<n<<endl;
        return true;
    }

    输出:

    实参的地址0x7ffcab293260
    值传递--函数操作地址0x7ffcab29323c
    after change1() n=10
    引用传递--函数操作地址0x7ffcab293260
    after change2() n=11
    指针传递--函数操作地址 0x7ffcab293260
    after change3() n=12

    引用的规则:
    1.引用被创建的同时必须被初始化(指针则可以在任何时候被初始化)
    2. 不能有NULL引用,引用必须与合法的存储单元关联(指针可以是NULL)
    3. 一旦引用被初始化,就不能改变引用的关系(指针则可以随时改变所指的对象)

    (12) 将参数作为常量还是非常量传递

    1) 只要一个形参(parameter)通过引用或指针传递其实参(argument)给一个函数,如果该函数既不修改这个参数也不存储其可写地址,那么这个形参就应该声明为常量。

    无论何时我们都可以合理地把一个指针或引用参数作为常量传递,并且我们都应该这么做。

    2) 避免将通过值传递给函数的形参声明为常量。
    通过值传递给函数的实参就是拷贝。将形参声明为常量会使它的值在该函数体不可变。

    3) 在由值、常量引用或常量指针传递实参的形参之前,考虑放置允许可修改访问的形参。

    值非常量,引用或者指针为常量。

    (13) 友元函数还是非友元函数

    避免不必要的友元关系,即使在单个组件内,也会影响维护成本。

    (14)内联函数还是非内联函数

    1. 如果函数体产生的对象代码大于等价的非内联函数调用本身产生的对象代码,那么应该避免声明该函数为inline。

    2.若编译器不会产生内联函数,那么应该避免将一个函数声明为inline

    (15) 基本类型

    类型                                             字节                                           范围
    char                                           1 个字节                              -128127 或者 0255                         
    unsigned char                                  1 个字节                                        0255                                   
    signed char                                    1 个字节                                     -128127
    int                                            4 个字节                              -21474836482147483647    
    unsigned int                                   4 个字节                                        04294967295
    signed int                                     4 个字节                               -21474836482147483647     
    short int (short)                              2 个字节                                   -3276832767                         
    unsigned short int (unsigned short)            2 个字节                                        065,535
    signed short int                               2 个字节                                   -3276832767
    long int                                       4 个字节                           -2,147,483,6472,147,483,647
    signed long int                                4 个字节                                    与 long int 相同
    unsigned long int                              4 个字节                                      04,294,967,295
    long long int                                  8个字节                                     2^63-12^63  
    signed long long int                           8个字节                                      2^63-12^63 
    unsigned long long int                         8个字节                                         到2^64+1  
    float                                          4 个字节                                +/- 3.4e +/- 38 (~7 个数字)
    double                                         8 个字节                               +/- 1.7e +/- 308 (~15 个数字)
    long double                                    8 个字节                               +/- 1.7e +/- 308 (~15 个数字)

    浮点类型是没有有符号和无符号的概念的,如无其他情况,一般来说函数接口类型都为int 或者 double。

    展开全文
  • JAVA8自定义函数接口

    千次阅读 2018-01-30 16:52:06
    函数接口:JDK1.8的特性,有且只有一个抽象方法的接口就是函数接口 @FunctionalInterface:该注解用于编译器校验该函数接口是否合法即用于限制一个接口中只有一个抽象方法方法 自定义函数接口 /** * 自定义...

    函数接口:JDK1.8的特性,有且只有一个抽象方法的接口就是函数接口

    @FunctionalInterface:该注解用于编译器校验该函数接口是否合法即用于限制一个接口中只有一个抽象方法方法

    自定义函数接口

    /**
     * 自定义函数接口
     *  函数接口只能有一个抽象方法
     *
     * @author haibin.tang
     * @create 2018-01-30 下午1:55
     **/
    @FunctionalInterface
    public interface JobFuntion {
    
        void execute();
    
    }
    public class Java8Characteristic {
    
    
        @Test
        public void stream() {
            //jdk8之前
            testJobFunction(new JobFuntion() {
                @Override
                public void execute() {
                    System.out.println("我是自定义函数接口");
                }
            });
            //使用lambada表达式 代码更简洁
            testJobFunction(() -> System.out.println("我是自定义函数接口"));
        }
    
    
        private void testJobFunction(JobFuntion jobFuntion) {
            jobFuntion.execute();
        }
    }
    
    展开全文
  • java8自定义函数接口

    千次阅读 2018-11-15 13:55:47
    java8开始可以自定义函数接口,方便开发人员使用lambda表达式,简化了代码量。 1.首先定义一个函数接口(使用泛型能过更好的适配所有对象的操作)   /** * Represents a function that accepts two ...

    java8开始可以自定义函数式接口,方便开发人员使用lambda表达式,简化了代码量。

    1.首先定义一个函数式接口(使用泛型能过更好的适配所有对象的操作)

        

    /**
     * Represents a function that accepts two argument and produces a result.
     *
     *
     * whose functional method is {@link #Do(Object)}.
     *
     * @param <A> the first of the input to the function
     * @param <B> the second of the result of the function
     * @param <C>  the return value
     * @since 1.8
     */
    @FunctionalInterface
    public interface MyFunction<A,B,C> {
    
        C Do(A a,B b);
    }

    2.直接运行main方法

    public class MyFunctionTest {
    
        public static void main(String[] args){
    
            MyFunction fun = (a,b) -> (int) a+ (int)b;
            System.out.println(fun.Do(1,2));
        }
    }

    以上是最简单的写法,显式的调用了Do方法。

    剩余的代码封装,其余的接口和抽象类的定义就看个人功底了。想要熟悉函数接口使用,可以去看看集合类的stream使用。

    也可以参考http://www.runoob.com/java/java8-functional-interfaces.html

    https://github.com/winterbe/java8-tutorial

    展开全文
  • Simulink代码生成:Step函数接口配置

    千次阅读 2021-03-30 21:08:00
    本文研究Simulink生成代码时的step函数的名称和参数。 文章目录1 问题引入2 配置过程3 代码生成4 总结 1 问题引入 在之前的一篇博客...通过更多的研究后,发现保持以前的建模方案,也可以通过配置生成特定接口的St

    本文研究Simulink生成代码时的step函数的名称和参数。

    1 问题引入

    在之前的一篇博客《Simulink代码生成:Simulink Function子系统及其代码》中,博主为了满足生成带有非空参数的函数这个需求,使用了Simulink Function子系统,基本上解决了问题。

    但是这样的方案将Simulink Function子系统作为顶层模型,完全不符合一般的建模方式。通过更多的研究后,发现保持以前的建模方案,也可以通过配置生成特定接口的Step函数。本文就将这个方法记录下来。

    2 配置过程

    本节简单演示一下Step函数接口配置的方法。

    1)建立一个简单的Simulink模型,包含一个输入接口、一个输出接口、一个Gain模块;
    在这里插入图片描述
    2)配置Embedded Coder的离散求解器和目标文件,参考《Simulink代码生成: Embedded Coder配置》一文,不再赘述;

    3)打开配置:Model Configuration Parameters – Code Generation – Interface – Config Model Functions;
    在这里插入图片描述
    4)在弹出的窗口中自定义 C Initialize Function Name 为 User_Initialize_Function ,定义 C Step Function Name 为 User_Step_Function ,然后勾选下面的 Configure arguments for Step function prototype,表示为函数原型配置参数;
    在这里插入图片描述
    5)点击下面的 Get default 按钮,就会检测到模型中的输入输出port,并提示配置;默认配置是把In1配置成 Value(也可以选指针、常量等),把Out1配置成 Pointer,把返回值配置成 Void ;名字分别可以手动修改成Input和Output,然后点击OK就完成了;
    在这里插入图片描述

    3 代码生成

    在第2章的配置后,Ctrl + B生成代码,打开C文件如下;
    在这里插入图片描述
    可以看到,Step函数和初始化函数都变成了配置的名字,并且Step函数中按照配置的要求,将Inport生成为传参的形式,将Outport生成为指针参数。

    然后打开头文件也可以看到对应的函数声明,也是自定义的。
    在这里插入图片描述
    在第二章中,也可以将输出端口配置为函数的返回值,或者输入端配置成其他形式,后面会相应地生成代码。

    4 总结

    Step函数接口配置是一个比较细小的配置项,但是可以满足生成传参函数的需求。对于数组、结构体等接口,也可以通过配置dimensions和Bus类型来达到,与Simulink Function子系统的配置方法类似。

    >>返回个人博客总目录

    展开全文
  • 在嵌入式开发中,经常需要将函数接口固化到Flash中的特定位置,供其他应用来调用。特别是层次分明的嵌入式开发(IAP编程),这种需求会变得更加强烈。今天介绍一种简单的方法,来实现这一功能。 1、先来分析下,在同...
  • Java函数式编程之Java8四大函数接口

    千次阅读 2019-07-12 20:16:09
    在Java8中,内置了四个核心函数接口,它们存在是Lamda表达式出现的前提,Lamda表达式想重写函数式接口中的唯一方法。 函数式接口与Lambda表达式之间的关系:lambda表达式相当于是一个行为,传入函数式接口中,进来...
  • 查看dll函数接口的参数的方法

    万次阅读 2019-06-17 02:21:06
    查看dll函数接口的方法,都有统一的方法,就是使用dependen工具,但是,这个工具看不了函数的参数,那么,这时候有什么方法,方法有二:一个使用逆向的方法,如使用IDA,二是利用vs的提示功能,详情待下回分解,可先...
  • Java8函数接口

    千次阅读 2020-09-20 01:21:16
    Java函数接口 函数接口概述 什么是函数接口函数接口就是只定义一个抽象方法的接口,但同时可以拥有默认方法,为了简化函数方法的实现可以使用lambda表达式,其基本语法如下: (parameters) -> expression #...
  • 现在客户给了一个dll文件,没有相关的说明文档,要求实现与该dll文件相同功能的dll,供上层程序调用,但是我现在只能用dll函数查看器看到函数名,并不知道具体的函数参数,这个怎么实现啊,求助各位大神啊!...
  • C语言的本质(15)——C语言的函数接口入门
  • 里面有详细的linux/unix的所有API与接口函数,这里可以查找很多你想用的东西
  • windows下查看动态库和静态库的函数接口在window下查看动态库的导出函数可以用vs自带的Dependenc工具;查看静态库的信息要用命令行来实现:dumpbin /LINKERMEMBER *.lib &gt; 1.txt查看动态库的信息要用命令行来...
  • 代码 void pyramid(int n){ int i, j, space; for(i=1; i <= n; i++) { space = n - i; for(j=0; j < space; j++) putchar(' '); for(j=0; j < i; j++) ... putcha...
  • 在实际工作中不通函数接口之间的调用以及不同文件之间函数接口的调用都很平常,但是在使用的时候会比较麻烦一些,有很多细节需要注意到
  • 函数接口定义: void input(); 该函数利用scanf从输入中获取学生的信息,并将其组织成单向链表。链表节点结构定义如下: struct stud_node { int num; /*学号*/ char name[20]; /*姓名*/ int score; /*成绩*/ ...
  • 一、函数接口 函数式接口(Functional Interface)就是一个有且仅有一个抽象方法,但是可以有多个非抽象方法的接口。 函数式接口可以被隐式转换为lambda表达式。 在开发中,函数式接口非常脆弱:只要开发者在该...
  • int sign(int x)//用户自定义函数,一般放在主函数前,可省去在主函数中的说明 { int temp; if(x>0) temp=1; else if(x==0)//切记,不能只有一个等号,两个等号是恒等的意思 temp=0; else temp=-1; return temp;/...
  • 必看:深入学习Java8中的函数接口

    千次阅读 2018-06-06 21:47:38
    因为Java8引入了函数接口,在java.util.function包含了几大类函数接口声明。这里第一篇主要研究一下Function相关的接口。 FunctionalInterface注解 Java8的新引入,包含函数式的设计,...
  • Java 8 函数接口

    千次阅读 多人点赞 2019-01-08 23:06:50
    可以包含Object里所有能重写的方法,因为即使接口包含像String toString()这样的抽象方法,它的实现类也会因继承了Object类,而再次对接口中的toString()方法进行实现。  作用: 方便直接用Lambda表达式构造出...
  • #include <stdio.h> void pyramid(int n) { int i,j; for(i=1;i<=n;i++) { for(j=1;j<i;j++) printf(" “); for(j=10-i;j>=1;j–) printf(”%d “,10-i); printf(”\n"); } ...pr...
  • #include <stdio.h> int sum(int m,int n) { int s=0,i; for(i=m;i<=n;i++) { s+=i; } return s; } int main() { int m,n,t; printf(“输入两个整数:”); scanf("%d%d",&m,&...n=t...
  • 什么是函数接口

    千次阅读 2012-07-12 14:32:43
    函数接口 就是函数的调用者与函数的实现者之间的关系描述。也就是 “返回值类型与返回值大小+函数名+参数值类型”  参考文献:http://learn.akae.cn/media/ch24.html
  • C++调用C文件中定义的函数接口

    千次阅读 2016-09-10 16:10:36
    今天测试了一下C++ 源文件中要调用C源文件定义的函数,最初在头文件中声明函数,C源文件和C++源文件中都include头文件,导致CodeBlock编译不通过。经过修改和测试,发现在C源文件中不能include头文件,并且应该在...
  • 什么是函数接口

    千次阅读 2020-06-02 12:17:17
    函数接口就是只定义一个抽象方法的接口,如下例子: Java API中也有一些函数接口 如Runnable接口类: Callable接口类: 同时只要接口只定义了一个抽象方法,那它就还是一个函数接口,哪怕这个接口有好多...
  • 函数、方法和接口

    千次阅读 2019-08-24 16:50:58
    1.4 函数、方法和接口 函数对应操作序列,是程序的基本组成元素。Go语言中的函数有具名和匿名之分:具名函数一般对应于包级的函数,是匿名函数的一种特例,当匿名函数引用了外部作用域中的变量时就成了闭包函数,...
  • 函数接口定义: ElementType FindKth( List L, int K ); 其中List结构定义如下: typedef struct LNode *PtrToLNode; struct LNode { ElementType Data; PtrToLNode Next; }; typedef PtrToLNode List; L是给...
  • 函数接口BiConsumer

    千次阅读 2018-03-08 16:27:17
    package sourcecode.analysis; /** * @Author: cxh * @CreateTime: 18/3/8 15:54 * @ProjectName: JavaBaseTest */ import java.util.Objects; ... * to operate via side... * 本函数接口特征: * 1.输入参数2个....
  • Java的函数接口简介

    千次阅读 2019-08-07 15:23:18
    所谓函数接口,指的是只有一个抽象方法的接口函数接口可以被隐式转换为Lambda表达式。 函数接口可以用@FunctionalInterface注解标识。 JDK1.8之前就出现了一些符合函数接口定义的接口: java.lang...
  • Java8中Function函数接口详解及使用

    千次阅读 多人点赞 2019-08-05 18:59:17
    函数接口1.1允许定义默认方法1.2允许定义静态方法1.3允许定义java.lang.Object的public方法1.4已有函数接口2.Function函数2.1Function1.函数接口 函数接口(Functional Interface)就是一个有且仅有一个抽象...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 1,617,003
精华内容 646,801
关键字:

函数接口