既然成员函数在内存中只有一份,那调用不同对象的成员函数时,函数内部是如何区分要操作哪个对象呢。
当调用一个非静态成员函数时,编译器会隐式地把调用该函数的对象的地址指针作为第一个参数传递进去,这就是this指针,类型是className* const。obj1.show()在编译器看来更像是show(&obj1)。
// 对于 obj1.show(),this 指向 obj1,所以 this->mA 就是 obj1.mA(值为10)。 |
// 对于 obj2.show(),this 指向 obj2,所以 this->mA 就是 obj2.mA(值为30)。 |
2.1.9 静态成员函数
在 C++ 中,静态成员函数(Static Member Function)是类的成员函数,但它不属于类的某个具体对象,而是属于类本身。静态成员函数可以直接通过类名调用,无需创建类的实例(对象),并且它只能访问类的静态成员(静态变量或其他静态函数),不能直接访问非静态成员(普通变量或普通函数)。
静态成员函数没有this指针,因此无法访问类的非静态成员(变量或函数),因为非静态成员属于对象实例。
声明周期与类相同,静态成员函数在程序加载时就被初始化,直到程序结束才销毁。
调用:
class MyClass { |
public: |
static void StaticFunction(); // 静态成员函数声明 |
}; |
MyClass::StaticFunction(); // 直接通过类名调用 |
空指针访问成员函数
核心思想是函数调用并不依赖与对象地址。成员函数是存放在代码区的,使用nullptr调用成员函数是可以调用的,只是传入的this指针是nullptr。
如果成员函数没有涉及到成员变量,可以正常执行。
涉及成员变量时会因为this->mA => nullptr->mA而导致程序崩溃。
类中存在虚函数
- 当一个类有虚函数时,编译器会为这个类创建一个隐藏的表,叫做虚函数表
vptr。这个表存放了所有虚函数的地址。 - 对于对象opj,obj->vptr->doSomething()
a. 通过对象指针 `p` 找到对象本身。b. 从对象的内存中读取 `vptr`(虚函数表指针)。c. 通过 `vptr` 找到虚函数表。d. 在虚函数表中查找 `doSomething` 的地址。e. 跳转到该地址执行函数。- 当一个类有虚函数时,编译器会为这个类创建一个隐藏的表,叫做虚函数表
2.1.10 虚函数
虚函数是在基类中使用virtual关键字声明的成员函数。它允许你在派生类中对该函数进行重写(Override),并且当你通过基类的指针或引用来调用该函数时,程序会动态地根据指针或引用实际所指向的对象类型,来调用相应派生类中的版本,而不是基类的版本。
核心目的是实现运行时多态,也成为动态绑定,就是用一个统一的接口,去处理多种不同类型的对象。
#include <iostream> |
// 基类:动物 |
class Animal { |
public: |
// 使用 virtual 关键字声明为虚函数 |
virtual void speak() { |
std::cout << "Some generic animal sound!" << std::endl; |
} |
}; |
// 派生类:狗 |
class Dog : public Animal { |
public: |
// 重写 speak 函数 (override关键字是C++11引入的,推荐使用,让意图更清晰) |
void speak() override { |
std::cout << "Woof! Woof!" << std::endl; |
} |
}; |
// 派生类:猫 |
class Cat : public Animal { |
public: |
void speak() override { |
std::cout << "Meow!" << std::endl; |
} |
}; |
int main() { |
Dog myDog; |
Cat myCat; |
Animal* animalPtr1 = &myDog; |
Animal* animalPtr2 = &myCat; |
std::cout << "Calling speak() via pointers (with virtual):" << std::endl; |
animalPtr1->speak(); // 现在它会正确地叫 "Woof! Woof!" |
animalPtr2->speak(); // 现在它会正确地叫 "Meow!" |
return 0; |
} |
2.2 C++运算符重载
什么是运算符重载:相对于某个class来说,重新定义已有的运算符,使得其工作在我们期待的情况下。例如
Vector v1(1, 2), v2(3, 4); |
Vector v3 = v1 + v2; // 希望实现向量相加 |
2.2.1 运算符重载的语法
- 成员函数的形式
// Vector: 返回值 |
// Vector:: : 表示这是一个成员函数,属于Vector类。 |
// const Vector& other: 表示一个常量引用,避免拷贝开销,保证只读 |
Vector Vector::operator+(const Vector& other) const; |
class Vector { |
public: |
double x, y; |
// 构造函数 |
Vector(double x = 0, double y = 0) : x(x), y(y) {} |
// 重载 + 运算符 |
Vector operator+(const Vector& other) const { |
return Vector(x + other.x, y + other.y); // 返回新对象 |
} |
}; |
int main() { |
Vector v1(1.0, 2.0); |
Vector v2(3.0, 4.0); |
Vector v3 = v1 + v2; // 调用 operator+,结果为 (4.0, 6.0) |
std::cout << "v3: (" << v3.x << ", " << v3.y << ")" << std::endl; |
return 0; |
} |
- 非成员函数的形式
通常使用friend关键字,友元函数。
class Vector { |
// ... 其他成员 ... |
friend Vector operator+(const Vector& a, const Vector& b); |
}; |
Vector operator+(const Vector& a, const Vector& b) { |
return Vector(a.x + b.x, a.y + b.y); |
} |
2.2.2 常见运算符重载
只要某个表达式里出现了你自定义的类型,并且用到了某个运算符,而这个运算符对该类型没有现成的、可用的实现,编译器就会去查找是否存在针对该类型、该运算符的重载函数。找到了就用,找不到就报错。
Vector& Vector::operator=(const Vector& other) { |
if (this != &other) { // 防止自赋值 |
x = other.x; |
y = other.y; |
} |
return *this; |
} |
int& Vector::operator[](int index) { |
if (index == 0) return x; |
else if (index == 1) return y; |
else throw std::out_of_range("Index out of range"); |
} |
// std::ostream& os是输出流对象,如std::cout |
std::ostream& operator<<(std::ostream& os, const Vector& v) { |
os << "(" << v.x << ", " << v.y << ")"; |
return os; |
} |
std::istream& operator>>(std::istream& is, Vector& v) { |
is >> v.x >> v.y; |
return is; |
} |
// 前置 ++ |
Vector& Vector::operator++() { |
++x; |
++y; |
return *this; |
} |
// 后置 ++(用 int 参数区分,没有逻辑原因,就是一个占位参数,用于区分) |
Vector Vector::operator++(int) { |
Vector temp = *this; |
++(*this); |
return temp; |
} |
// 关系运算符重载 |
bool operator<(const Person& other) const { |
return age < other.age; |
} |
bool operator>(const Person& other) const { |
return age > other.age; |
} |
bool operator<=(const Person& other) const { |
return age <= other.age; |
} |
bool operator>=(const Person& other) const { |
return age >= other.age; |
} |
输入输出流重载说明:std::ostream& operator<<(std::ostream& os, const Vector& v)
首先这是一个函数,函数名字是operator<<,函数的输出类型是std::ostream&,输入类型是std::ostream& const Vector& v相当于(std::cout << v)的重载后的输出可以是std::cout以便格式化输出Vector类型之后还能够继续链式输出其他内容。
// 输入输出流重载 |
#include <iostream> |
struct Vector { |
double x, y; |
}; |
// 输出流重载 |
std::ostream& operator<<(std::ostream& os, const Vector& v) { |
os << "(" << v.x << ", " << v.y << ")"; |
return os; |
} |
// 输入流重载 |
std::istream& operator>>(std::istream& is, Vector& v) { |
is >> v.x >> v.y; |
return is; |
} |
int main() { |
Vector v1, v2; |
// 输入 |
std::cout << "Enter Vector 1 (x y): "; |
std::cin >> v1; // 例如输入: 1.0 2.0 |
std::cout << "Enter Vector 2 (x y): "; |
std::cin >> v2; // 例如输入: 3.0 4.0 |
// 输出 |
std::cout << "Vector 1: " << v1 << std::endl; // 输出: Vector 1: (1.0, 2.0) |
std::cout << "Vector 2: " << v2 << std::endl; // 输出: Vector 2: (3.0, 4.0) |
return 0; |
} |