非静态成员函数是声明于类的成员说明中,不带 staticfriend 说明符的函数。

  1. class S {
  2. int mf1(); // 非静态成员函数声明
  3. void mf2() volatile, mf3() &&; // 可为 cv 限定或引用限定
  4. int mf4() const { return data; } // 可内联定义
  5. virtual void mf5() final; // 可为虚函数,可使用 final/override
  6. S() : data(12) {} // 构造函数亦是成员函数
  7. int data;
  8. };
  9. int S::mf1() { return 7; } // 若不内联定义,则必须定义于命名空间

允许任何函数声明,并带有仅可用于非静态成员函数的额外语法元素:finaloverride 说明符,纯说明符,cv 限定符,引用限定符,以及成员初始化列表

可以用以下方式调用类 X 的非静态成员函数

1) 对 X 类型的对象使用类成员访问运算符调用

2) 对派生自 X 的类的对象调用

3) 从 X 的成员函数体内直接调用

4) 从派生自 X 的类的成员函数体内调用直接调用

在任何其他类型的对象上调用类 X 的成员函数导致未定义行为。

在 X 的非静态成员函数的体内,任何解析为 X 或 X 的某个基类的非类型非静态成员的标识表达式 E(例如一个标识符),均被变换为成员访问表达式 (*this).E 除非它已是成员访问表达式的一部分)。模板定义语境中不会发生这种变换,因而可能必须明确地对某个名字前附 this->,以使其成为待决的名字。

  1. struct S {
  2. int n;
  3. void f();
  4. };
  5. void S::f() {
  6. n = 1; // 变换为 (*this).n = 1;
  7. }
  8. int main() {
  9. S s1, s2;
  10. s1.f(); // 更改 s1.n
  11. }

在 X 的非静态成员函数体内,任何解析到 X 或 X 的某个基类的静态成员、枚举项或嵌套类型的无限定标识,均被变换为对应的有限定标识。

  1. struct S {
  2. static int n;
  3. void f();
  4. };
  5. void S::f() {
  6. n = 1; // 变换为 S::n = 1;
  7. }
  8. int main() {
  9. S s1, s2;
  10. s1.f(); // 更改 S::n
  11. }

const、volatile 及引用限定的成员函数

非静态成员函数可声明为带有 const、volatile 或 const volatile 限定符(这些限定符出现在函数声明中的形参列表之后)。cv 限定性不同的函数具有不同类型,从而可以相互重载。

在 cv 限定的函数体内,this 指针被 cv 限定,例如 const 成员函数中,只能正常地调用其他 const 成员函数。(如果应用了 const_cast,或通过不涉及 this 的访问路径,则仍可调用非 const 成员函数。)

  1. #include <vector>
  2. struct Array {
  3. std::vector<int> data;
  4. Array(int sz) : data(sz) {}
  5. // const 成员函数
  6. int operator[](int idx) const {
  7. // this 具有类型 const Array*
  8. return data[idx]; // 变换为 (*this).data[idx];
  9. }
  10. // non-const member function
  11. int& operator[](int idx) {
  12. // this 具有类型 Array*
  13. return data[idx]; // 变换为 (*this).data[idx]
  14. }
  15. };
  16. int main()
  17. {
  18. Array a(10);
  19. a[1] = 1; // OK:a[1] 的类型是 int&
  20. const Array ca(10);
  21. ca[1] = 2; // 错误:ca[1] 的类型是 int
  22. }

非静态成员函数可声明为无引用限定符,带有左值引用限定符(函数名后的 & 记号),或带有右值引用限定符(函数名后的 && 记号)。在重载决议中,按下列方式对待类 X 的非静态 cv 限定成员函数:


- 无引用限定符:隐式对象形参具有到 cv 限定的 X 的左值引用类型,并额外允许绑定到右值隐含对象实参
- 左值引用限定符:隐式对象形参具有到 cv 限定的 X 的左值引用类型
- 右值引用限定符:隐式对象形参具有到 cv 限定的 X 的右值引用类型




  1. #include <iostream>
    struct S {
    void f() & { std::cout << "lvalue\n"; }
    void f() &&{ std::cout << "rvalue\n"; }
    };

    int main(){
    S s;
    s.f(); // 打印“lvalue”
    std::move(s).f(); // 打印“rvalue”
    S().f(); // 打印“rvalue”
    }




注意:不同于 cv 限定性,引用限定性不改变 this 指针的性质:在右值引用限定的函数中,*this 仍是左值表达式。
(C++11 起)

虚函数和纯虚函数

非静态成员函数可声明为纯虚函数。细节见虚函数抽象类

特殊成员函数

构造函数析构函数是非静态成员函数,在其声明中使用特殊的语法(细节见其相应页面)。

一些成员函数是特殊的:在某些环境下,即使用户不定义编译器也会定义它们。它们是:



- 移动构造函数
(C++11 起)


- 移动赋值运算符
(C++11 起)

特殊成员函数以及比较运算符 (C++20 起)是仅有能被预置的函数,即使用 = default 替代函数体进行定义(细节见其相应页面)

示例

运行此代码

  1. #include <iostream>
  2. #include <string>
  3. #include <utility>
  4. #include <exception>
  5.  
  6. struct S {
  7. int data;
  8.  
  9. // 简单的转换构造函数(声明)
  10. S(int val);
  11.  
  12. // 简单的显式构造函数(声明)
  13. explicit S(std::string str);
  14.  
  15. // const 成员函数(定义)
  16. virtual int getData() const { return data; }
  17.  
  18. };
  19.  
  20. // 构造函数的定义
  21. S::S(int val) : data(val) {
  22. std::cout << "ctor1 called, data = " << data << '\n';
  23. }
  24.  
  25. // 此构造函数拥有 catch 子句
  26. S::S(std::string str) try : data(std::stoi(str)) {
  27. std::cout << "ctor2 called, data = " << data << '\n';
  28. } catch(const std::exception&) {
  29. std::cout << "ctor2 failed, string was '" << str << "'\n";
  30. throw; // 构造函数的 catch 子句应该始终再抛出异常
  31. }
  32.  
  33. struct D : S {
  34. int data2;
  35. // 带默认实参的构造函数
  36. D(int v1, int v2 = 11) : S(v1), data2(v2) {}
  37.  
  38. // 虚成员函数
  39. int getData() const override { return data*data2; }
  40.  
  41. // 左值限定的赋值运算符
  42. D& operator=(D other) & {
  43. std::swap(other.data, data);
  44. std::swap(other.data2, data2);
  45. return *this;
  46. }
  47. };
  48.  
  49. int main()
  50. {
  51. D d1 = 1;
  52. S s2("2");
  53. try {
  54. S s3("not a number");
  55. } catch(const std::exception&) {}
  56. std::cout << s2.getData() << '\n';
  57.  
  58. D d2(3, 4);
  59. d2 = d1; // OK :赋值给左值
  60. // D(5) = d1; // 错误:无适合的 operator= 重载
  61. }

输出:

  1. ctor1 called, data = 1
  2. ctor2 called, data = 2
  3. ctor2 failed, string was 'not a number'
  4. 2
  5. ctor1 called, data = 3