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

本文详细介绍了C++中的内联函数与友元的概念及其应用。内联函数通过inline关键字减少函数调用开销,提升运行效率。友元机制允许非成员函数访问类的私有成员,适用于实现类外的操作,如重载运算符。文中还探讨了它们的注意事项及最佳实践。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

内联函数

什么是内联函数呢?

以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);

但是,常规的非成员函数不能直接访问类的私有数据,所以就有了友元函数

创建友元函数

创建友元函数第一步是把函数的原型放在类的声明中,并在原型声明前加上关键字friend

friend 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  << "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;

友元声明可以位于公有、私有或保护部分,其所在的位置无关紧要

完.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值