C++学习——多态
1、什么是多态
多态允许一个类的对象被当作另一个类的对象来使用,即同一个接口,使用不同的实例而执行不同操作。
多态分为两种:编译时多态和运行时多态。

2、静态多态
2.1 函数重载
允许在同一作用域内定义多个同名函数,只要它们的参数列表不同即可。
基本条件:
-
函数名称必须相同
-
参数列表必须不同(参数类型、参数个数或参数顺序)
-
返回类型可以相同也可以不同(但仅返回类型不同不足以构成重载)
void print(int a)
{
cout << "void print(int a)" << endl;
}
void print(int a, int b)
{
cout << "void print(int a, int b)" << endl;
}
void print(double a)
{
cout << "void print(double a)" << endl;
}
int print(int a, double b)
{
cout << "int print(int a, double b)" << endl;
}
float print(double a, int b)
{
cout << "float print(double a, int b)" << endl;
}
int main()
{
print(10 ,10);
print(3.14f);
print(10);
print(3.14f, 10);
print(10,3.14f);
}
2.1.1 函数重载调用规则
优先级从高到低:
-
精确匹配
当实参类型与形参类型完全一致时。void func(int); void func(double); func(10); // 调用 func(int) - 精确匹配 func(3.14); // 调用 func(double) - 精确匹配 -
类型提升
-
char, short 提升为 int
-
float 提升为 double
-
bool 提升为 int
void func(int); void func(double); char c = 'A'; func(c); // 调用 func(int) - char 提升为 int float f = 3.14f; func(f); // 调用 func(double) - float 提升为 double -
-
标准转换(隐式类型转换)
-
算术类型转换
int→long, int→double, double→long double
-
指针转换(如派生类指针转基类指针)
-
bool 转换
实例1:整数间类型转换
void func(long x) { cout << "long: " << x << endl; } void func(double x) { cout << "double: " << x << endl; } int main() { int i = 42; func(i); // 调用 func(long) , int 转 long 是标准转换 // 注意:不是类型提升,因为 int→long 不属于整数提升范围 }实例2:浮点类型间的转换
void func(long double x) { cout << "long double: " << x << endl; } int main() { double d = 3.14; func(d); // 调用 func(long double) // double 转 long double 是标准转换 }实例3:整数和浮点数的类型转换
void func(double x) { cout << "double: " << x << endl; } int main() { int i = 10; func(i); // 调用 func(double) // int 转 double 是标准转换 }实例4:派生类指针转基类指针
class Base { public: virtual void show() { cout << "Base" << endl; } }; class Derived : public Base { public: void show() override { cout << "Derived" << endl; } }; void func(Base* ptr) { ptr->show(); } int main() { Derived d; func(&d); // 派生类指针转基类指针是标准转换 // 输出: Derived (多态行为) }实例5:数组转指针
void func(int* arr) { cout << arr[0] << endl; } int main() { int arr[5] = {1, 2, 3, 4, 5}; func(arr); // 数组转指针是标准转换 }实例6:类类型转bool类型
#include<iostream> using namespace std; class MyClass { public: explicit operator bool() const { return isValid; } bool isValid = true; }; void func(bool b) { cout << (b ? "true" : "false") << endl; } int main() { MyClass obj; func(obj); // 调用 operator bool() 是用户定义转换 // 如果去掉 explicit,可以直接转换 // 有 explicit 需要 static_cast<bool>(obj) }实例7:数值类型转bool类型
void func(bool b) { cout << (b ? "true" : "false") << endl; } int main() { int i = 10; func(i); // int 转 bool (非零→true) double d = 0.0; func(d); // double 转 bool (0.0→false) } -
-
用户定义转换
class MyClass { public: operator int() const { return 42; } }; void func(int); void func(double); MyClass obj; func(obj); // 调用 func(int) - 使用用户定义的转换这里为什么转int类型不转double类型呢?
对于 func(obj),编译器会考虑以下两种可能的转换路径:
路径一(选择 func(int)):
MyClass → int(通过 operator int()) 然后精确匹配 func(int)路径二(选择 func(double)):
MyClass → int(通过 operator int()) 然后 int → double(标准转换) 最后匹配 func(double)为什么选择第一个呢?
用户定义转换序列的优先级:当存在多个可行的用户定义转换序列时,编译器会
选择最短的转换序列。
2.2 运算符重载
2.2.1 基本概念
定义:赋予已有运算符新的功能,使其能用于自定义类型的操作
本质:运算符重载是特殊的函数重载
限制:
-
不能创建新运算符
-
不能改变运算符的优先级和结合性:编译期间已经确定。
-
不能改变运算符的操作数个数 -
部分运算符不能被重载
-
至少一个操作数必须是用户自定义类型(不能对两个内置类型进行运算符重载)
2.2.2 运算符重载的两种形式
1. 成员函数形式:左操作数一定是当前对象this。
class ClassName
{
public:
ClassName operator+(const ClassName& other) const
{
// 实现加法操作
}
};
ClassName a, b, c;
c = a + b; // 等价于 a.operator+(b)
2. 友元函数形式
对于两个不同类型的对象进行运算,或者左操作数不是当前类的对象,使用非成员函数方式。为了访问类的私有成员,需要将函数声明为类的友元。
class Complex
{
friend Complex operator+(const Complex& a, const Complex& b);
};
Complex operator+(const Complex& a, const Complex& b)
{
return Complex(a.real + b.real, a.imag + b.imag);
}
a、基本友元运算符重载
class Complex
{
private:
double _real;
double _imag;
public:
Complex(){}
Complex(double real, double imag):_real(real), _imag(imag){}
void Print()
{
cout << _real << "+" << _imag << endl;
}
friend Complex operator+(const Complex &a, const Complex &b);
};
Complex operator+(const Complex &a, const Complex &b)
{
Complex tmp;
tmp._imag = a._imag + b._imag;
tmp._real = a._real + b._real;
return tmp;
}
关键点:
-
friend
声明在类内部,但函数实现仍在类外部 -
友元函数
可以访问类的所有私有成员 -
调用时和使用成员函数形式完全一样
b、混合类型运算(必须用友元)
class Complex
{
private:
double _real;
double _imag;
public:
Complex():_real(0), _imag(0){}
Complex(double real, double imag):_real(real), _imag(imag){}
void Print()
{
cout << _real << "+" << _imag << endl;
}
friend Complex operator+(double real, const Complex& a);
};
Complex operator+(double real, const Complex& a)
{
Complex tmp;
tmp._real = real + a._real;
tmp._imag = a._imag;
return tmp;
}
int main()
{
Complex c1(10.0f, 3.14f);
Complex c2 = 3.14f + c1;
c2.Print();
return 0;
}
为什么必须用友元:
左操作数不是类对象,无法使用成员函数形式。需要访问私有成员,普通非成员函数无法做到
c、输入输出运算符重载
class Complex
{
private:
double _real;
double _imag;
public:
Complex():_real(0), _imag(0){}
Complex(double real, double imag):_real(real), _imag(imag){}
friend istream& operator>>(istream &is, Complex &c);
friend ostream& operator<<(ostream &os, const Complex &c);
};
istream& operator>>(istream &is, Complex &c)
{
is >> c._real;
is >> c._imag;
return is;
}
ostream& operator<<(ostream &os, const Complex &c)
{
os << "real = " << c._real << " imag = " << c._imag;
return os;
}
问题:
-
返回值必须是流引用类型,否则无法实现链式调用。istream& operator>>(istream& is, ...) // 不是 istream ostream& operator<<(ostream& os, ...) // 不是 ostream这样才能
支持链式调用:cin >> c1 >> c2; 和 cout << c1 << c2; -
为什么输出可以使用 os << "real = " << c._real << " imag = " << c._imag; 无视了第一个参数必须是流引用类型,第二个参数必须是自定义常引用类型?
// 自己的重载 ostream &operator<<(ostream &os, const Complex &c) { os << "real = " << c._real << " imag = " << c._imag; // 这里发生了什么? return os; }// 标准库的重载(简化版) // 简化版的标准库实现(非完整代码) namespace std { class ostream { public: // 基本类型重载 ostream& operator<<(int val); ostream& operator<<(double val); // ... 其他基本类型(bool, long, 等) // 字符串重载 ostream& operator<<(const char* str); // 关键重载 };标准库的 const char* 重载始终存在,与你是否定义 Complex 的重载无关。自己写的重载和标准库的重载是互补的:
自写的 operator<<(ostream&, const Complex&) 只负责处理 Complex 类型。
标准库的 operator<<(ostream&, const char*) 只负责处理字符串。
编译器会智能组合它们:当你的函数内使用 os << "text" 时,自动选择标准库版本。
2.2.3 常用运算符重载示例
-
算术运算符
此处注意:operator+需要返回一个新对象而不是修改现有对象,数学本质中加法是产生一个新值,而不是修改原有值。
class Complex { private: double _real; double _imag; public: Complex(){} Complex(double real, double imag):_real(real), _imag(imag){} void Print() { cout << _real << "+" << _imag << endl; } Complex operator+(const Complex &other) const { Complex tmp; tmp._real = this->_real + other._real; tmp._imag = this->_imag + other._imag; return tmp; } // 更高效的方法 // Complex operator+(const Complex& other) const // { // return Complex(_real + other._real, _imag + other._imag); // } }; int main() { Complex c1(10.0f, 3.14f); Complex c2(5.0f, 3.14f); Complex c3 = c1 + c2; c3.Print(); return 0; } -
关系运算符
bool operator==(const Complex &c) const { if(this->_imag == c._imag && this->_real == c._real) return true; return false; } -
下标运算符 []
需要实现两个,
const和非const版本,当对象被声明为const时,只能调用其常量成员函数。如果没有const版本,代码将无法编译。class Arr { public: Arr(int n):size(n), arr(new int[n]){} int& operator[](int index) { if (index < 0 || index >= size) { cout << "index error !" << endl; } return arr[index]; } // 重载[]运算符(常量版本,只读) const int& operator[](int index) const { if (index < 0 || index >= size) { cout << "index error !" << endl; } return arr[index]; } // 获取数组大小 int getSize() const { return size; } private: int *arr; int size; }; int main() { Arr myArr(5); // 写入数据 for (int i = 0; i < myArr.getSize(); ++i) { myArr[i] = i * 10; // 使用非常量版本 } // 读取数据 for (int i = 0; i < myArr.getSize(); ++i) { cout << myArr[i] << " "; // 使用非常量版本 } const Arr constArr(5); // 使用const版本读取数据 cout << constArr[0]; // 如果没有const版本,这里会编译失败! } -
自增/自减运算符
前置需要返回引用,后置需要返回临时对象。后置版本必须带一个int类型参数(实际调用时不传值,仅用于语法区分),编译器会自动传递一个int类型的参数0。
class Num { public: Num(){} Num(int num):_num(num){} void Print() { cout << _num << endl; } // 前置++ Num& operator++() { _num++; return *this; } // 后置++ Num operator++(int) { Num old = *this; // 保存原值 _num++; return old; // 返回旧值 } // 前置-- Num& operator--() { _num--; return *this; } // 后置-- Num operator--(int) { Num temp = *this; _num--; return temp; } private: int _num; };
2.2.4 特殊运算符重载
-
函数调用运算符 ()
class Adder { public: int operator()(int a, int b) { return a + b; } }; Adder add; int sum = add(3, 4); // 使用方式如同函数 -
类型转换运算符
class Rational { public: operator double() const { return numerator/(double)denominator; } private: int numerator, denominator; }; Rational r(1, 2); double d = r; // 自动转换为0.5 -
new/delete 运算符
class ClassName { public: void* operator new(size_t size) { /* 自定义内存分配 */ } void operator delete(void* p) { /* 自定义内存释放 */ } };
2.2.5 运算符重载的注意事项
-
保持语义一致性:重载的运算符行为应与内置类型类似
-
谨慎重载 &&、|| 和 ,:这些运算符通常有短路求值特性,重载后会失去这一特性
-
考虑对称性:如 a + b 和 b + a 应该结果相同
-
优先选择成员函数形式:特别是需要访问私有成员的运算符
-
必要时使用友元:当第一个操作数不是类对象时(如 <<、>>)
-
不能重载的运算符:
作用域解析 :: 成员访问 . 成员指针访问 .* 条件运算符 ?: sizeof 和 typeid -
重载运算符的参数限制:
至少有一个参数是类类型或枚举类型
不能改变运算符的优先级和结合性
-
赋值运算符的特殊性:
如果没有定义,编译器会生成默认的赋值运算符
通常需要处理自赋值问题

浙公网安备 33010602011771号