C++逻辑设计——函数设计之函数接口说明

本文详细探讨了C++中函数设计的关键因素,包括运算符函数、虚函数、纯虚函数、静态与非静态成员函数、常量成员函数、访问权限、返回类型、参数传递方式等方面的选择。强调了函数接口设计应考虑的可读性、多态性和效率问题,以及如何避免隐藏和不必要耦合。此外,还讨论了友元函数和内联函数的使用策略。

在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。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值