MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

C++类内部定义成员函数的属性特点

2023-06-286.1k 阅读

C++类内部定义成员函数的属性特点

一、成员函数的定义方式

在C++中,类的成员函数可以在类内部直接定义,也可以在类外部进行定义。在类内部直接定义成员函数时,编译器会将其隐式地视为内联函数(除非该函数过于复杂,编译器无法进行内联优化)。例如:

class Rectangle {
private:
    int width;
    int height;
public:
    // 在类内部定义成员函数
    void setDimensions(int w, int h) {
        width = w;
        height = h;
    }
    int getArea() {
        return width * height;
    }
};

而在类外部定义成员函数,需要使用作用域解析运算符::。例如:

class Circle {
private:
    double radius;
public:
    void setRadius(double r);
    double getArea();
};

void Circle::setRadius(double r) {
    radius = r;
}

double Circle::getArea() {
    return 3.14159 * radius * radius;
}

二、访问权限相关特点

  1. 与类的访问修饰符紧密相关 成员函数的访问权限取决于其在类中定义的位置,即与类的访问修饰符(publicprivateprotected)紧密相关。定义在public部分的成员函数可以在类的外部被调用,而定义在private部分的成员函数只能在类的内部被其他成员函数调用。例如:
class BankAccount {
private:
    double balance;
    // 私有成员函数,用于更新余额
    void updateBalance(double amount) {
        balance += amount;
    }
public:
    BankAccount(double initialBalance) : balance(initialBalance) {}
    // 公有成员函数,用于存款
    void deposit(double amount) {
        updateBalance(amount);
    }
    // 公有成员函数,用于获取余额
    double getBalance() {
        return balance;
    }
};

在上述代码中,updateBalance函数是私有成员函数,只能在BankAccount类的内部被调用,如deposit函数中调用了它。而depositgetBalance函数是公有成员函数,可以在类的外部被调用。

  1. 访问类的私有和保护成员 成员函数可以访问类的私有和保护成员,这是类封装特性的重要体现。即使成员函数在类外部定义,只要它属于该类,就有权访问类的私有和保护成员。例如:
class Secret {
private:
    int privateData;
public:
    Secret(int data) : privateData(data) {}
    // 在类外部定义,但仍可访问私有成员
    int getPrivateData() {
        return privateData;
    }
};

这里getPrivateData函数在类外部定义,但由于它是Secret类的成员函数,所以可以访问privateData私有成员。

三、内联特性

  1. 隐式内联 如前文所述,在类内部定义的成员函数,编译器通常会将其视为内联函数。内联函数的好处是减少函数调用的开销,因为在编译时,编译器会将内联函数的代码直接嵌入到调用该函数的地方,而不是像普通函数那样进行函数调用的跳转。例如:
class Point {
private:
    int x;
    int y;
public:
    // 隐式内联函数
    void setCoordinates(int a, int b) {
        x = a;
        y = b;
    }
    int getX() {
        return x;
    }
    int getY() {
        return y;
    }
};

在使用Point类的地方,如:

int main() {
    Point p;
    p.setCoordinates(10, 20);
    int xVal = p.getX();
    int yVal = p.getY();
    return 0;
}

编译器在编译main函数时,可能会将setCoordinatesgetXgetY函数的代码直接嵌入到相应的调用位置,从而提高程序的执行效率。

  1. 编译器的优化策略 然而,编译器并非一定会将类内部定义的成员函数进行内联处理。如果函数体过于复杂,例如包含大量的循环、递归或复杂的逻辑判断,编译器可能会放弃内联优化,因为内联这样的函数可能会导致代码膨胀,反而降低程序的性能。例如:
class ComplexCalculation {
private:
    int data[1000];
public:
    // 函数体复杂,可能不会被内联
    void performComplexCalculation() {
        for (int i = 0; i < 1000; ++i) {
            for (int j = 0; j < 1000; ++j) {
                data[i] += j;
            }
        }
    }
};

在这种情况下,编译器可能认为将performComplexCalculation函数内联会使代码体积大幅增加,从而选择不进行内联。

四、与对象实例的关系

  1. 隐含的this指针 类的成员函数隐含一个this指针,该指针指向调用该成员函数的对象实例。通过this指针,成员函数可以访问对象的成员变量和调用其他成员函数。例如:
class Person {
private:
    std::string name;
    int age;
public:
    Person(const std::string& n, int a) : name(n), age(a) {}
    void printDetails() {
        std::cout << "Name: " << this->name << ", Age: " << this->age << std::endl;
    }
};

printDetails函数中,this->namethis->age通过this指针访问对象的成员变量。实际上,即使不显式地使用this指针,如std::cout << "Name: " << name << ", Age: " << age << std::endl;,编译器也会隐式地使用this指针来访问成员变量。

  1. 不同对象调用成员函数的独立性 每个对象都有自己独立的成员变量,而成员函数的代码是共享的。当不同对象调用同一个成员函数时,成员函数操作的是调用该函数的对象的成员变量。例如:
class Counter {
private:
    int count;
public:
    Counter() : count(0) {}
    void increment() {
        ++count;
    }
    int getCount() {
        return count;
    }
};

int main() {
    Counter c1, c2;
    c1.increment();
    c2.increment();
    c2.increment();
    std::cout << "c1 count: " << c1.getCount() << std::endl;
    std::cout << "c2 count: " << c2.getCount() << std::endl;
    return 0;
}

在上述代码中,c1c2是两个不同的Counter对象,它们各自调用increment函数时,操作的是自己的count成员变量,所以c1count为1,c2count为2。

五、成员函数的重载与覆盖

  1. 重载 在同一个类中,可以定义多个同名但参数列表不同的成员函数,这就是成员函数的重载。编译器会根据调用时提供的参数类型和个数来决定调用哪个重载版本的函数。例如:
class MathOperations {
public:
    int add(int a, int b) {
        return a + b;
    }
    double add(double a, double b) {
        return a + b;
    }
    int add(int a, int b, int c) {
        return a + b + c;
    }
};

在使用MathOperations类时:

int main() {
    MathOperations mo;
    int result1 = mo.add(2, 3);
    double result2 = mo.add(2.5, 3.5);
    int result3 = mo.add(2, 3, 4);
    return 0;
}

这里根据传递的参数类型和个数,分别调用了不同的add函数重载版本。

  1. 覆盖(重写) 在继承体系中,派生类可以定义与基类虚函数同名且参数列表相同的成员函数,这就是函数的覆盖(重写)。覆盖发生在派生类对基类虚函数进行重新定义时,目的是实现多态行为。例如:
class Shape {
public:
    virtual double getArea() {
        return 0.0;
    }
};

class Rectangle : public Shape {
private:
    double width;
    double height;
public:
    Rectangle(double w, double h) : width(w), height(h) {}
    // 覆盖基类的getArea函数
    double getArea() override {
        return width * height;
    }
};

class Circle : public Shape {
private:
    double radius;
public:
    Circle(double r) : radius(r) {}
    // 覆盖基类的getArea函数
    double getArea() override {
        return 3.14159 * radius * radius;
    }
};

在使用时:

int main() {
    Shape* s1 = new Rectangle(5.0, 3.0);
    Shape* s2 = new Circle(4.0);
    std::cout << "Rectangle area: " << s1->getArea() << std::endl;
    std::cout << "Circle area: " << s2->getArea() << std::endl;
    delete s1;
    delete s2;
    return 0;
}

这里通过基类指针调用getArea函数,实际调用的是派生类中重写的版本,从而实现了多态。

六、静态成员函数

  1. 定义与特点 类可以定义静态成员函数,静态成员函数属于类,而不是类的对象实例。静态成员函数没有隐含的this指针,因为它不与任何特定的对象实例相关联。静态成员函数只能访问类的静态成员变量,不能直接访问非静态成员变量。例如:
class Company {
private:
    static int employeeCount;
    std::string employeeName;
public:
    Company(const std::string& name) : employeeName(name) {
        ++employeeCount;
    }
    static int getEmployeeCount() {
        return employeeCount;
    }
};

int Company::employeeCount = 0;

在上述代码中,getEmployeeCount是静态成员函数,它只能访问静态成员变量employeeCount

  1. 调用方式 静态成员函数可以通过类名直接调用,也可以通过对象实例调用,但通过对象实例调用静态成员函数在语义上不太清晰,一般推荐通过类名调用。例如:
int main() {
    Company c1("Alice");
    Company c2("Bob");
    int count1 = Company::getEmployeeCount();
    int count2 = c1.getEmployeeCount();
    return 0;
}

这里Company::getEmployeeCount()c1.getEmployeeCount()都可以调用静态成员函数getEmployeeCount,但Company::getEmployeeCount()更清晰地表明这是类级别的操作。

七、const成员函数

  1. 定义与作用 const成员函数是指在函数声明和定义中使用const关键字修饰的成员函数。const成员函数承诺不会修改对象的成员变量(除非这些成员变量被声明为mutable)。其作用主要是为了在对象处于常量状态时也能调用成员函数,同时保证对象的状态不会被意外修改。例如:
class ImmutableData {
private:
    int value;
public:
    ImmutableData(int v) : value(v) {}
    // const成员函数
    int getValue() const {
        return value;
    }
};

在上述代码中,getValue函数是const成员函数,它不会修改ImmutableData对象的value成员变量。

  1. 与非const成员函数的重载 一个类可以同时定义const和非const版本的同名成员函数,这构成了函数重载。编译器会根据对象是否为常量来决定调用哪个版本的函数。例如:
class MutableData {
private:
    int data;
public:
    MutableData(int d) : data(d) {}
    int getData() const {
        return data;
    }
    int& getData() {
        return data;
    }
};

int main() {
    const MutableData cd(10);
    MutableData md(20);
    int value1 = cd.getData();
    int& value2 = md.getData();
    return 0;
}

这里cd是常量对象,所以调用const版本的getData函数,而md是非常量对象,调用非const版本的getData函数,该函数返回一个可修改的引用。

八、友元成员函数

  1. 定义与作用 友元成员函数是指在一个类中声明为另一个类的友元的成员函数。友元函数可以访问该类的私有和保护成员,打破了类的封装性。其作用通常是为了实现一些需要访问多个类的私有成员的特殊操作。例如:
class Box {
private:
    int length;
    int width;
    int height;
public:
    Box(int l, int w, int h) : length(l), width(w), height(h) {}
    // 声明另一个类的成员函数为友元
    friend class VolumeCalculator;
};

class VolumeCalculator {
public:
    static int calculateVolume(const Box& box) {
        return box.length * box.width * box.height;
    }
};

在上述代码中,VolumeCalculator类的calculateVolume成员函数被声明为Box类的友元,因此可以访问Box类的私有成员lengthwidthheight

  1. 注意事项 虽然友元函数提供了访问类私有成员的便利,但过度使用会破坏类的封装性,降低代码的可维护性和安全性。因此,在使用友元函数时,应该谨慎考虑其必要性,并尽量将其使用范围限制在最小程度。

九、成员函数模板

  1. 定义与特点 类的成员函数也可以是模板函数,称为成员函数模板。成员函数模板允许编写通用的、可适应多种数据类型的成员函数。例如:
class Container {
public:
    // 成员函数模板
    template <typename T>
    void printValue(T value) {
        std::cout << "Value: " << value << std::endl;
    }
};

在使用Container类时:

int main() {
    Container c;
    c.printValue(10);
    c.printValue(3.14);
    c.printValue("Hello");
    return 0;
}

这里printValue是一个成员函数模板,可以接受不同类型的参数,并根据实际传入的参数类型进行实例化。

  1. 与类模板的关系 成员函数模板可以存在于普通类中,也可以存在于类模板中。当存在于类模板中时,成员函数模板的类型参数可以与类模板的类型参数相同,也可以不同。例如:
template <typename T>
class GenericContainer {
private:
    T data;
public:
    GenericContainer(T value) : data(value) {}
    // 成员函数模板
    template <typename U>
    void printDataWithExtra(U extra) {
        std::cout << "Data: " << data << ", Extra: " << extra << std::endl;
    }
};

在使用GenericContainer类模板时:

int main() {
    GenericContainer<int> gc(10);
    gc.printDataWithExtra(20);
    gc.printDataWithExtra(3.14);
    return 0;
}

这里GenericContainer是类模板,printDataWithExtra是其成员函数模板,它的类型参数U与类模板的类型参数T不同,可以接受不同类型的额外参数。

通过对C++类内部定义成员函数的上述属性特点的深入了解,开发者能够更加灵活、高效地编写C++代码,充分发挥C++面向对象编程的强大功能。无论是实现复杂的业务逻辑,还是构建可复用的软件组件,对成员函数属性特点的掌握都是至关重要的。