您的位置:首页 > 编程语言 > C语言/C++

【C++基础】浅谈内联、静态成员和友元

2018-03-28 19:56 561 查看

内联函数

什么是内联函数呢?
以inline修饰的函数叫做内联函数,例:inline int MAX(int a, int b) {
return a > b ? a : b;
}MAX函数是比较两个整型变量a和b的大小的函数,被inline修饰后,就称为了内联函数
那么,内联函数有什么作用呢?
C++编译器在对代码进行编译的时候,在遇到内联函数的时候,就会把代码展开,没有函数压栈的开销
看上去好像跟宏函数有点像?

那么,宏函数和内联函数的区别是什么呢?

在C中,宏函数的有点很多,宏函数可以避免函数压栈的开销,提高了运行效率,但是,预处理器在处理宏代码的时候,可能会出现不可避免的边际效应,例如:#define MAX(a, b) (a) > (b) ? (a) : (b)上面的宏代码,在下面语句中,展开如下:result = MAX(i, j) + 2;
result = (i) > (j) ? (i) : (j) + 2;+操作符的优先级高于?:操作符,所以这个宏并不能总达到我们所期望的那样#define MAX2(a, b) ((a) > (b) ? (a) : (b))如果改成这样,加上括号,一般情况下已经没有问题了,但是在下面的情况下还是会出问题:result = MAX2(i++, j);
result = (i++) > (j) ? (i++) : (j);上面的情况i自加了两次,所以也会出现问题
宏还有一个缺点就是它的不可调试性,而内联函数是可以调试的,虽然它也是在编译阶段把代码展开,但是它在debug阶段是不会展开的,会像普通的函数和一样,而在release版本才会产生内联
宏会导致代码的可读性比较差,而内联函数并不会
对于C++而言,宏还有一个缺点就是无法操作,私有类的成员
所以,在C++中,内联函数即有宏代码的优点,又没有宏代码的缺点,所以尽量用内联来代替宏代码
关于内联函数有以下几点需要注意的:
·inline是一种以空间换时间的做法,省去调用函数额开销。所以代码很长或者有循环/递归的的函数不适宜使用内联
·nline对于编译器而言只是一个建议,编译器会自动优化,如果定义为inline的函数体内有循环/递归等等,编译器优化时会忽略掉内联
·inline必须函数定义放在一起,才能成为内联函数,仅将inline放在声明前是不起不作用的
·定义在类内的成员函数默认定义为内联函数
而在Google C++编码规范中则规定得更加明确和详细:

内联函数:Tip: 只有当函数只有 10 行甚至更少时才将其定义为内联函数.
定义:当一个函数被声明为内联函数的时候,编译器会将其内联展开,而不是按照通常的函数调用机制去调用
优点:当函数体较小的时候,可以提高代码的运行效率
缺点:滥用内联导致代码臃肿,使运行速度减慢
结论:一个较为合理的经验准则是, 不要内联超过 10 行的函数,函数内部有循环和递归的函数也不要使用内联

类的静态成员

在类中,被static修饰的成员叫做静态成员,类的静态成员是该类的所有对象所共享的class Date{
public:
Date(int year = 1900, int month = 1, int day = 1)
: _year(year),
_month(month),
_day(day){
//构造函数
_year = year;
_month = month;
_day = day;
//每调用一次构造函数,就给count++
++count;
}
inline void Display() const {
cout << "year:" << _year << endl;
cout << "month" << _month << endl;
cout << "day" << _day << endl;
}
static void PrintCount() {
cout << count << endl;
}
private:
int _year;
int _month;
int _day;
private:
//统计这个日期类被调用的次数
static int count;
};
在上面的日期类,我们想统计日期类被创建对象的次数,所以我们用静态成员变量count来统计
注意,静态成员变量是没有银行的this指针的,所以调用的时候用域名::来调用
静态成员变量在类中仅仅是声明,所以要在类外面定义并初始化int Date::count = 0;总结:
·静态成员变量通过static变量修饰
·静态成员变量需要在类外面单独分配内存空间

·静态成员变量的声明周期不依赖于任何一个对象

·可以通过类名直接访问公有静态成员变量
·所有对象共享静态成员变量
·可以通过对象直接访问公有静态成员变量
·静态成员变量在程序中位于全局数据区

友元

C++控制对类对象私有部分的访问,但有时候,这种控制太过严格,以至于不适合特定的编程问题,在这种情况下,C++提供了 另外一种形式的访问权限:友元
友元分为三种,有:

1.友元函数

让函数成为类的友元,可以赋予该函数与类的成员函数相同的访问权限
在类重载二元运算符的时候,常常需要友元,为什么呢?举个栗子
在Time对象乘以实数的时候,如下,A和B都代表Time对象:
A = B * 2.75;
这个表达式将会被转化为下面的成员函数来调用:
A = B.operator*(2.75);
但是下面的语句呢?
A = 2.75 * B;
因为第一个表达式不对应与成员函数,所以编译器不能使用成员函数调用来替换该表达式
解决这种问题,要么归并*号右边必须是个对象,要么使用非成员函数来解决,非成员函数不是由对象调用的,所以它的第一个参数不是this指针,如果调用的话可以这样:A  = operator*(2.75 * B);
但是,常规的非成员函数不能直接访问类的私有数据,所以就有了友元函数
创建友元函数
创建友元函数第一步是把函数的原型放在类的声明中,并在原型声明前加上关键字friendfriend Time operator*(double m, const Time & t);·虽然友元函数operator*()是在类声明中声明的,但是它不是成员函数
·虽然它不是成员函数,但是它与成员函数的访问权限相同
第二步就是定义函数,注意,它不是成员函数,不要使用Time::限定符,而且也不用加上friend关键字,定义如下:Time operator*(doublt m, const Time& t) {
//TODO
}常用的友元:重载<<运算符

我们以前在类中定义一个show()函数来打印对象
如果这样会更好 :cout << 类名;
看上去好像可以使用<<运算符重载,可是,<<运算符重载,一般是这样的://<<运算符重载
void operator<<(ostream& cout) {
    cout << _hour << ":" << _minute << ":" << _second << endl;
}看上去没啥问题,但是调用的时候:cout << Time;
会出现错误的,因为this指针,在类中的成员函数,第一个形参是this指针,所以<<运算符重载的话,必须得这样写:
Time << cout;
可这样不是看起来怪怪的,不易于代码的可读性,所以就需要用到友元函数
在类中的声明如下:friend void operator<<(ostream& cout, const Time& t);定义如下:void operator<<(ostream& cout, const Time& t) {
cout << t._hour << ":" << t._minute << ":" << t._second << endl;
}这样就可以这样调用了:cout << Time;
可是我们发现,cout 
ba3e
<< "Time" << Time ;
类似于这样的表达式是行不通的,它好像并不能像cout一样使用
在介绍如何解决这个问题之前,我先解释一下表达式的返回值
a = 20;这个表达式的返回值时多少?答案是20
所以看一下下面这个语句:

a = b = c ;
在C/C++中,类似这样的连续赋值是如何做到的呢?
a = (b = c);
由于赋值表达式是从右往左的,所以这个表达式可以用括号像上面这样扩起来,(b = c)这个表达式的返回值就是b,然后再有a = b,这样就可以做到连续赋值了
那么如果我们想像这样使用cout << "Time" << Time << "end";
我们可让友元函数的返回值是cout, ((cout << "Time") << Time) << "end";
这样就可以做到像cout一样连续使用了,声明代码如下:friend ostream& operator<<(ostream& cout, const Time& t);定义代码:ostream& operator<<(ostream& cout, const Time& t) {
cout << t._hour << ":" << t._minute << ":" << t._second << endl;
}有一个问题,友元是否违反了 OOP(面向对象编程)的数据隐藏原则,友元可以让非成员函数访问类的私有数据
这关观点太片面了,我们应该把友元函数看做类的扩展接口的组成部分,比如double乘以Time和Time乘以double是完全相同的,这是C++语法上的差别,而不是概念上的差别,可以使用友元函数和类方法,用同一个用户接口这两种操作。
而且,只有类声明可以决定哪一个函数是友元,类声明仍然控制了哪些函数可以访问私有数据。
总之,类方法和友元只是表达类接口的两种不同机制

2.友元类

整个类可以是另一个类的友元。友元类的每个成员函数都是另一个类的友元函数,都可访问另一个类中的保护或私有数据成员。
下面的声明可以使Time称为友元类:
friend class Time;
友元声明可以位于公有、私有或保护部分,其所在的位置无关紧要

完.
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息