C++ 友元、内联以及静态成员

宏函数&&内联函数


宏函数

  • 宏函数是在编译期间展开的,没有函数压栈的开销
  • 我们首先定义一个宏函数
//定义一个比较大小的宏函数
#define MAX(a,b) a>b?a:b
  • 观察下面程序的运行结果:
    这里写图片描述
  • 我们期待得到的max值为8(4>2,4+4=8),但是运行的结果却不是我们预料的值。这是因为+的有限级高于三目运算符,编译器在运算是是这样的过程:2+4=6,4>2?4:6,这样得到的结果就是4
  • 所以定义宏函数必须考虑优先级问题。为了避免这种问题,保险的宏函数的写法应该是这样的:
#define MAX(a,b) ((a)>(b)?(a):(b))
  • 这样会使得我们代码的可读性变差
  • 我们再来看一个实例:
//定义一个Swap函数
#define Swap(a,b) int tmp=a;a=b;b=tmp;
  • 观察下面程序的运行结果:
    这里写图片描述
  • 我们调用一次Swap函数,运行结果正确;再次调用,编译不通过(tmp重定义);这种情况在我们的自定义函数中没有的,为了解决这个问题,我们有下面两种方案:
    这里写图片描述
  • 方案一:给宏函数调用加上{},使其在一个局部域中,出了这个作用域,局部变量就被销毁
    这里写图片描述
  • 方案二:给宏定义加{}

通过上面的简单举例,我们发现虽然宏函数能提高程序运行效率,但是对于编写程序的人来说宏函数的书写要比我们普通函数麻烦的多,并且使得代码可读性变差

宏函数优点总结:

  • 避免了函数压栈的开销,提高了程序运行的效率

宏函数缺点总结:

  • 不方便调试
  • 没有类型安全检查
  • 使得代码可读性变差,不容易控制(程序编写容易出错,并且比较难维护)

内联函数

  • 内联函数:以inline关键字修饰的函数称为内联函数
  • 内联函数的作用:C++在编译时会在调用内联函数的地方展开函数代码,没有函数压栈的开销,可提高程序运行的效率
  • 内联函数在Debug(调试版本)下是有函数栈帧的开销的,在Release(优化版本)下没有函数栈帧的开销,才会产生内联
  • Debug:
    这里写图片描述
  • Release:
    这里写图片描述

关于内联,有以下几点需要说明:

  • 内联是以空间来换取时间,如果函数代码比较长或者调用的次数比较多,不建议用内联,因为这样会使程序代码膨胀
  • 如果定义为内联的函数体内有递归调用,编译器优化时会忽略掉内联
  • 定义内联函数时,inline关键字必须与函数定义放在一起,这样才能构成内联函数,若只是将inline放在函数声明前是不起作用的
  • 内联对于编译器而言只是一个建议,有编译器决定是否内联
  • 定义在类内的成员函数默认为内联函数,函数调用时直接在调用处展开,无函数栈帧开销。建议:C++中类的成员函数如果比较短,则不用把声明和定义分离开

内联函数相对于宏函数的优点:

  • 代码可读性高
  • 内联函数可以访问类的私有成员,而宏函数不能

因为,内联函数有宏函数的优点,没有宏函数的缺点,所以在C++中我们可以使用内联来代替宏

C++中有哪些技术可以代替宏?

  • const
  • 内联
  • 枚举

友元


C++面向对象的三大特性:封装、继承、多态。封装性限制了类外对象对类内私有成员的访问,但在某些特定场合下,我们往往需要从类外访问类的私有成员,为了处理这种情况,C++为我们提供了另一种方式的访问形式:友元。通过关键字friend来定义

友元函数

  • 友元函数:若将一个类外的函数在类内声明为友元函数,则这个函数和类的成员函数对类的成员有相同的访问权限
  • 比如我们可以这样定义类对象的打印函数:
//第二步,定义函数,因为这个函数并不是类的成员函数,所以不需要使用A::限定符,而且也不用加上friend关键字
void show(const A& a)
{
    //友元函数具有和类的成员函数相同的访问权限,所以可以访问类的私有成员
    cout << "a=" << a._a << endl;
}

class A
{
public:
    //创建友元函数第一步是把函数的原型放在类的声明中,并在原型声明前加上关键字friend
    friend void show(const A& a);
private:
    int _a;
};

int main()
{
    A a;
    show(a);
    return 0;
}
  • 上面的实例只是为了演示友元函数的创建方法,事实上我们一般不用这种方法来创建对象的打印函数

友元函数的常用场景<<、>>运算符的重载

  • 我们定义一个Date类:

class Date
{
public:
    Date(int year = 2018, int month = 5, int day = 9)
        :_year(year)
        ,_month(month)
        ,_day(day)
    {}

    void Display()
    {
        cout << _year << "-" << _month << "-" << _day << endl;
    }
private:
    int _year;
    int _month;
    int _day;
};
  • 我们前面学习的时候,要打印一个日期是通过对象调用Display()函数来进行打印的,今天我们来实现一下输入输出运算符的重载,我们期待可以通过cout<
    void operator<<(ostream& cout)
    {
        cout << _year << "-" << _month << "-" << _day << endl;
    }

    cout << d;
  • 我们定义了上面的输出运算符重载,并且用上面的语句调用它,但是编译不通过,这是为什么呢?
    这里写图片描述
  • 这是因为隐含的this指针,输出运算符重载作为类的成员函数,是有一个隐含的this指针的,这样的话按照我们上面的写法左操作数是隐含的this指针,右操作数是我们的cout,所以我们应该这样调用:
d<<cout;
  • 程序经过运行得到了正确的结果:
    这里写图片描述
  • 但是这样输出有点别扭,对于输出运算符我们还是希望cout在左边,要输出的对象在右边,那么我们就要考虑将this指针和cout参数位置进行交换,很明显,类内是无法定义这样的函数的。
  • 所以我们考虑在类外定义输出运算符重载,但是要想在类外访问我们类内的私有成员,我们的友元函数就派上用场了。下面是我们通过友元函数定义的输出运算符重载:
//类外定义
void operator<<(ostream& cout, const Date& d)
{
    cout << d._year << "-" << d._month << "-" << d._day << endl;
}

//类内声明
friend void operator<<(ostream& cout, const Date& d);
  • 运行结果:
    这里写图片描述
  • 我们还应该考虑一个问题,我们在使用输出语句的时候经常会这样使用:
cout<<"Date="<<d;
  • 如果按照我们上面的定义方法,没有返回值则我们的输出运算符重载不能支持连续输出,所以我们应该给其一个返回值cout,下面是我们终极版本的输入输出运算符重载:
//类外定义
ostream& operator<<(ostream& cout, const Date& d)
{
    cout << d._year << "-" << d._month << "-" << d._day << endl;
    return cout;
}

istream& operator>>(const istream& cin,Date& d)
{
    cin >> d._year
}
//类内声明
friend ostream& operator<<(ostream& cout, const Date& d);
friend istream& operator>>(const istream& cin,Date& d);

这里写图片描述

  • 对于输入运算符:首先,输入对象是由构造函数构造出来的,在输入对象前对象已经构造好了,对于Date类,为了避免输入非法日期,在重载输入运算符重载时应该对输入对象进行非法检查

友元类

  • 如果我们想在一个AA类中访问Date类中的成员,则我们可以将AA类声明为Date类的友元类,友元类的每个成员函数都是另一个类的友元函数,都可访问另一个类中的保护或私有数据成

关于友元,有以下几点说明:

  • 友元是单向关系,AA是Date的友元类,则AA可以访问Date类的保护或者私有成员,但是Date类不能访问AA类的保护或者私有成员
  • 友元尽量不要使用,因为破坏了C++的封装性

类的静态成员


  • 首先提一个问题:我们如何统计出一个类创建了多少个对象?
  • 既然这个次数和每个对象都有关,那么我们不能将其定义成一个私有成员变量。我们应该定义一个全局变量,每一个对象都可以访问到,没创建一个对象,++一次
  • 类的静态成员:在类中,被static修饰的成员叫做静态成员,类的静态成员是该类的所有对象所共享的
  • 解决上述问题的代码:
class Date{  
public:  
    Date(int year = 2018, int month = 5, int day = 9)  
    : _year(year)  
    ,_month(month) 
    ,_day(day)
    {  
        //每调用一次构造函数,就给count++  
        ++_count;  
    }  
    void PrintCount() 
    {  
        cout << _count << endl;  
    }  
private:  
    int _year;  
    int _month;  
    int _day;  
private:  
    //统计这个日期类被调用的次数,定义为静态成员变量
    static int _count;  
};  
  • 上面只是静态成员变量一个声明,若想定义(初始化)它,我们需要在类外进行如下操作:
int Date::_count=0;
  • 这是因为静态成员变量是没有this指针的,所以调用的时候用域名::来调用
  • 我们如何来获得_count的值呢?因为_count为私有的,所以我们要想访问只能通过类的成员函数进行访问,调用的时候只能通过对象调用:
    void Count() 
    {  
        cout << _count << endl;  
    }  

    //调用
    Date d;
    Date d1;
    d.Count();
    d1.Count();
  • 通过上面两个对象调用得到的结果都是2,不过这种调用方式容易引起混淆,既然_count是属于所有对象共有的,那么Count()函数也是所有对象共有的,所以我们可以将其定义为静态成员函数,然后通过访问静态成员的方式访问:
Date::Count();
  • 同样需要说明的是:静态成员函数是无this指针的

猜你喜欢

转载自blog.csdn.net/aurora_pole/article/details/80234835