C++:成员函数和非成员函数的区别

48

C++中成员函数和非成员函数有什么区别?


2
有趣的是,我原以为答案会很枯燥,但看起来我们对此有非常不同的看法! - Matthieu M.
6个回答

41

一个成员函数(下面称为方法)与一个自由函数(下面称为函数)之间有几个区别。

首先,让我们简单地说一下它们并不是完全不同的。目标代码通常可以编译成 C(或汇编),这些是没有方法概念的过程式语言。方法和函数都像子例程一样被调用。

既然这个问题解决了,让我们来看看它们之间的区别。它们可以分为两类:概念上的和语法上的。

语法上的

语法是任何语言中最显而易见的部分,所以也是最容易处理的部分。

首先要注意:C++ (以及其他一些语言)中有两种不同类型的方法,即 static 方法和普通方法。

两种类型的方法都可以完全访问类内部(protectedprivate 部分),当然还可以访问类的 public 接口。

static 方法等同于 friend 函数(除了某些作用域上的差异)。

在普通方法中,一个特殊关键字(例如 C++ 中的 this)允许访问调用该方法的当前对象(通过 .->.*->* 运算符)。

普通方法可以是 const 和/或 volatile 限定的,使其适用于(分别是)const 和/或 volatile 限定的对象。例如,不能在 const 对象上调用非 const 方法。这可以看作在方法内对 this 加以限定,即 void Foo::bar() const 具有类型为 Foo const*this

一个普通方法可以被标记为 virtual。虚拟性通过启用重写来实现运行时多态性。这个机制在这里不再详细说明,让我们注意到函数不能是虚函数。

常常忽略的一点是,方法(包括静态和普通方法)是在类内部范围内声明的。这对于名称查找(其他方法或属性/变量的查找)非常重要,因为它意味着从类外部声明的元素中进行查找时,类的元素具有优先权。

由于在属性或方法之前使用this->限定符并不是强制性的,在普通方法中很方便,但可能会引入微妙的错误。在静态方法中,它避免了通过类名限定要访问的静态属性和方法。

既然主要的语法差异已经确定,那么让我们检查概念上的差异。

从概念上讲

OOP通常是关于将状态和行为(针对该状态)联系在一起的。这是通过创建类来完成的,该类组合属性(状态)和行为(方法),并(理论上)声称只有方法才能作用于状态。因此,在OOP中,方法负责实现类的行为。

这些方法参与封装状态(使客户端摆脱实现细节)和保持类不变式(有关类状态的语句,从其诞生到其消亡始终成立,无论您对其做什么)。

C++

如我们之前所见,在C++中,这是通过使用不同的访问级别(publicprotectedprivate)并将非公共级别的访问权限授予受限制的代码部分来完成的。通常情况下,属性将是私有的,因此只能由类方法(以及一些朋友,用于语法怪癖)访问。

注意:我敦促您不要使用protected属性,因为很难追踪它们的修改,并且由于派生类的集合是无限的......它们的实现不能容易地改变。

不过要注意,C++ 不鼓励在接口中添加过多的方法。

问题在于,由于方法负责维护不变量,方法越多,责任就会分散,导致更难追踪错误和确保正确性。此外,由于方法依赖于类内部,所以改动成本更高。

相反,在 C++ 中,通常建议编写最少量的方法,并将其余行为委托给非friend函数(只要不增加太多成本)。

  • 请参阅 Sutter 在Monolith Unstrung中对std::string的看法。
  • Sutter 强调了委托给非友元方法的重要性,在其Interface Principle中指出,与类一起提供的函数(在同一文件或命名空间中)并使用该类的函数逻辑上是类接口的一部分。他在《Exceptional C++》中再次强调了这一点。

这个答案变得有点啰嗦了,但我怀疑我可能忽略了其他人认为关键的差异...... 哦好吧。


22

非静态的成员函数有一个隐式的this参数,而非成员函数则没有。

语法上,在.->操作符的左侧传递这个隐式参数,例如like.so()like->so(),而不是作为函数参数so( like )

同样地,当声明一个成员函数时,你需要在它所属的类中进行声明:

class Class {
public:
    void a_public_member_function();
};

非成员函数在任何类外声明(C++称之为“命名空间范围”)。

(非静态) 成员函数也可以是 虚函数,但非成员函数和静态成员函数不行。


1
请确保清楚地回答这个问题,不要将“非静态”成员函数与“静态”成员函数混淆。 - Johannes Schaub - litb

10

在一个类的对象上调用非静态成员函数,该函数会隐式地拥有代表当前对象的this指针,通过这个指针,它可以轻松访问其他成员并具有完全访问权限(即访问private成员)。

非成员函数没有隐式的this指针。在下面的示例中,bar是成员函数,而freebar不是。它们两个或多或少做相同的事情,但请注意bar如何通过this获取隐式对象指针(仅bar具有访问foo成员的特权,freebar只能访问公共成员)。

class foo {
public:

    void bar() {
        this->x = 0; // equivalent to x = 0;
    }

    int x;
};

void freebar(foo* thefoo) {
   thefoo->x = 1;
}


// ... 
foo f;
f.bar();
// f.x is now 0

freebar(&f);
// f.x is now 1

从语义上讲,成员函数不仅仅是一个具有隐式this参数的函数。它旨在定义对象的行为(例如,汽车对象将具有drive(), stop()作为成员函数)。

请注意,还存在静态成员函数,它们具有完全的权限,但不会得到隐式的"this",也不通过类的实例调用(而是通过类的完整名称进行调用)。


this 是语言实现的遗留问题。个人认为应该强调方法作用于特定对象的关系,而不是关注如何实现这种关系。在99.9%的情况下,您实际上并不使用 this,但仍然与对象的成员进行交互(我唯一使用它的地方是在 operator= 中返回对自身的引用)。 - Martin York
我稍微编辑了一下我的文本,强调了成员函数的语义含义。 - Alexander Gessler
1
@Martin 这是一种实现遗留问题,但对于理解C++中的差异及其后果至关重要。 - Alexander Gessler
我认为这需要更细致的区分。如果一个成员函数是非静态的,它才能访问this - Johannes Schaub - litb
添加了关于static成员函数的注释。 - Alexander Gessler
@Alexander Gessler:我不同意。事实上,我认为这会妨碍新手理解面向对象的概念;因为他们会把这个实现细节的知识应用到语言的其他部分。这个概念应该从面向对象的角度来解释,而不是从C++如何实现这个概念的角度来解释。正如上面所解释的,你几乎永远不需要使用this指针。如果你从例子中删除“this”,代码仍然完全相同。 - Martin York

7
在下面的代码中,f()Sample类的成员函数,而g()是非成员函数:
class Sample
{
  void f();
};

void g();

很简单。由于f()是类Sample的成员,因此它被称为类Sample的成员函数。而由于g()不属于任何类,因此它被称为非成员函数。


3

对象上调用成员函数并可以访问类的字段。

成员函数可以通过virtual关键字实现多态性,这对于面向对象编程是必不可少的。


1
成员函数会在实例上被调用,同时有一个可用的“this”指针;而非成员函数则没有。

这是否意味着,除了静态函数之外,类的所有函数都是成员函数? - Luke
@Luke:是的。(我写这个时候想着肯定会有人指出我忘记了什么晦涩的东西)。附言:我不是你的父亲(愿第四的力量与你同在)。 - Martin York
@Luke,不是的,它并不会混淆静态和非静态成员函数。那些回答将静态成员函数与非静态成员函数混淆的都是错误的。请参考@mmutz的答案以获得正确的描述。 - Johannes Schaub - litb

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接