C++ 基础常见面试题总结(中)
本文采用面试问答形式,系统梳理 C++ 中指针与引用、内存管理、面向对象的核心知识。⭐ 标记为高频重点题。
一、指针与引用
⭐ 指针和引用的区别?
| 特性 | 指针 | 引用 |
|---|---|---|
| 本质 | 存储地址的变量 | 变量的别名 |
| 是否可为空 | ✅ 可以为 nullptr | ❌ 不能为空 |
| 是否可变 | ✅ 可以改变指向 | ❌ 绑定后不可更改 |
| 是否需要初始化 | 可以不初始化 | 必须在声明时初始化 |
| 内存占用 | 占用指针大小(4/8 字节) | 通常不占额外空间(编译器优化) |
| 多级 | 支持多级指针 int** | 无多级引用 |
sizeof | 指针自身大小 | 被引用对象的大小 |
| 使用方式 | 需要 * 解引用 | 直接使用 |
cpp
int a = 10;
// 指针
int* p = &a;
*p = 20; // 通过解引用修改
p = nullptr; // 可以改变指向
// 引用
int& r = a; // 必须初始化
r = 30; // 直接使用,等同于 a = 30
// int& r2; // ❌ 编译错误:引用必须初始化
🌈 选择建议:
- 优先使用引用:更安全,语法更简洁
- 使用指针:需要"可空"、"可变指向"或操作动态内存时
⭐ 什么是悬空指针(Dangling Pointer)和野指针(Wild Pointer)?
| 类型 | 定义 | 原因 |
|---|---|---|
| 悬空指针 | 指向已释放内存的指针 | delete 后未置空 |
| 野指针 | 未初始化的指针 | 声明后未赋值 |
| 空指针 | 值为 nullptr | 主动设置 |
cpp
// 悬空指针
int* p = new int(42);
delete p; // 内存释放
// *p = 10; // ❌ 未定义行为!p 成为悬空指针
p = nullptr; // ✅ 好习惯:释放后置空
// 野指针
int* q; // 未初始化,指向随机地址
// *q = 10; // ❌ 未定义行为!
// 正确做法
int* safe = nullptr; // 初始化为空
⚠️ 防范措施:
- 指针初始化为
nullptrdelete后立即置nullptr- 使用智能指针替代原始指针
⭐ 什么是左值和右值?什么是左值引用和右值引用?
| 概念 | 含义 | 特征 | 示例 |
|---|---|---|---|
| 左值(lvalue) | 有地址、可取地址的表达式 | 可以出现在 = 左边 | 变量、数组元素 |
| 右值(rvalue) | 临时值、不可取地址 | 只能出现在 = 右边 | 字面量、临时对象 |
cpp
int a = 10; // a 是左值,10 是右值
int& lr = a; // 左值引用:绑定到左值
// int& lr2 = 10; // ❌ 左值引用不能绑定到右值
int&& rr = 10; // 右值引用(C++11):绑定到右值
// int&& rr2 = a; // ❌ 右值引用不能绑定到左值
const int& cr = 10; // ✅ 常量左值引用可以绑定右值(特例)
⭐ 什么是移动语义(Move Semantics)?
移动语义(C++11)允许窃取临时对象的资源,避免不必要的深拷贝:
cpp
class BigData {
int* data_;
size_t size_;
public:
// 拷贝构造:深拷贝,昂贵
BigData(const BigData& other) : size_(other.size_) {
data_ = new int[size_];
std::copy(other.data_, other.data_ + size_, data_);
std::cout << "Copy!\n";
}
// 移动构造:转移资源,高效
BigData(BigData&& other) noexcept
: data_(other.data_), size_(other.size_) {
other.data_ = nullptr; // 让源对象处于有效但空的状态
other.size_ = 0;
std::cout << "Move!\n";
}
};
BigData CreateData() {
BigData temp(1000);
return temp; // 触发移动构造(而非拷贝)
}
BigData a = CreateData(); // 输出 "Move!"
BigData b = a; // 输出 "Copy!"(a 是左值)
BigData c = std::move(a); // 输出 "Move!"(std::move 将左值转为右值引用)
// ⚠️ 此后 a 处于"已移动"状态,不应再使用其内容
⭐ std::move 的作用?
std::move 本身不移动任何东西,它只是将左值强制转换为右值引用,从而触发移动构造/移动赋值:
cpp
std::string s1 = "Hello";
std::string s2 = std::move(s1); // s1 的内容被移动到 s2
// s1 现在是空字符串(或处于有效但未指定的状态)
std::vector<std::string> vec;
std::string s = "World";
vec.push_back(std::move(s)); // 移动而非拷贝
⚠️ 注意:
std::move之后,原对象处于有效但未指定的状态,只能对其执行析构或重新赋值。
⭐ 什么是完美转发(Perfect Forwarding)?
完美转发通过 std::forward 保持参数的左值/右值属性:
cpp
template <typename T>
void Wrapper(T&& arg) { // 万能引用
Process(std::forward<T>(arg)); // 完美转发
}
void Process(int& x) { std::cout << "lvalue\n"; }
void Process(int&& x) { std::cout << "rvalue\n"; }
int a = 42;
Wrapper(a); // 输出 "lvalue"(a 是左值,T 推导为 int&)
Wrapper(42); // 输出 "rvalue"(42 是右值,T 推导为 int)
二、内存管理
⭐ new / delete 和 malloc / free 的区别?
| 特性 | new / delete | malloc / free |
|---|---|---|
| 所属 | C++ 运算符 | C 标准库函数 |
| 类型安全 | ✅ 返回具体类型指针 | ❌ 返回 void*,需强转 |
| 构造/析构 | ✅ 自动调用 | ❌ 不调用 |
| 异常处理 | 失败抛出 std::bad_alloc | 失败返回 NULL |
| 可重载 | ✅ 可以重载 operator new | ❌ |
cpp
// C++ 方式
int* p1 = new int(42); // 分配 + 初始化
delete p1; // 调用析构 + 释放
int* arr = new int[10]; // 分配数组
delete[] arr; // 释放数组(必须用 delete[])
// C 方式
int* p2 = (int*)malloc(sizeof(int)); // 仅分配内存
*p2 = 42; // 需手动初始化
free(p2); // 仅释放内存
// 对象的区别最明显
class Foo {
public:
Foo() { std::cout << "Constructor\n"; }
~Foo() { std::cout << "Destructor\n"; }
};
Foo* f1 = new Foo(); // 输出 "Constructor"
delete f1; // 输出 "Destructor"
Foo* f2 = (Foo*)malloc(sizeof(Foo)); // 不调用构造函数!
free(f2); // 不调用析构函数!
⭐ 什么是内存泄漏?如何避免?
内存泄漏:动态分配的内存未被释放,导致程序持续占用内存。
cpp
// 典型泄漏场景
void Leak() {
int* p = new int(42);
// 忘记 delete p; → 内存泄漏!
}
void LeakOnException() {
int* p = new int(42);
DoSomething(); // 如果抛出异常
delete p; // 这行永远执行不到!
}
避免方法:
| 方法 | 说明 |
|---|---|
| ⭐ 使用智能指针 | unique_ptr / shared_ptr 自动管理生命周期 |
| RAII 原则 | 资源在构造时获取,析构时释放 |
| 容器管理 | 用 vector 替代原始数组 |
| 工具检测 | Valgrind、AddressSanitizer |
cpp
// ✅ 使用智能指针避免泄漏
void NoLeak() {
auto p = std::make_unique<int>(42);
DoSomething(); // 即使抛异常,p 也会自动释放
// 不需要手动 delete
}
⭐ 智能指针有哪些?区别是什么?
| 类型 | 所有权 | 引用计数 | 拷贝 | 适用场景 |
|---|---|---|---|---|
unique_ptr | 独占 | 无 | ❌ 禁止拷贝,可移动 | 单一所有者 |
shared_ptr | 共享 | 有 | ✅ 拷贝增加引用计数 | 多个所有者 |
weak_ptr | 观察 | 不增加计数 | — | 打破循环引用 |
cpp
#include <memory>
// unique_ptr —— 独占所有权
auto up = std::make_unique<int>(42);
// auto up2 = up; // ❌ 编译错误:禁止拷贝
auto up2 = std::move(up); // ✅ 移动所有权
// shared_ptr —— 共享所有权
auto sp1 = std::make_shared<int>(100);
auto sp2 = sp1; // 引用计数变为 2
std::cout << sp1.use_count(); // 2
// weak_ptr —— 弱引用(不增加引用计数)
std::weak_ptr<int> wp = sp1;
if (auto sp3 = wp.lock()) { // 提升为 shared_ptr
std::cout << *sp3;
}
⭐ shared_ptr 的循环引用问题是什么?如何解决?
cpp
class B; // 前向声明
class A {
public:
std::shared_ptr<B> bPtr;
~A() { std::cout << "A destroyed\n"; }
};
class B {
public:
std::shared_ptr<A> aPtr; // ❌ 循环引用!
~B() { std::cout << "B destroyed\n"; }
};
void Problem() {
auto a = std::make_shared<A>();
auto b = std::make_shared<B>();
a->bPtr = b; // a → b,b 的引用计数 = 2
b->aPtr = a; // b → a,a 的引用计数 = 2
// 函数结束后,a 和 b 的引用计数各减 1,变为 1
// 永远不会减到 0 → 永远不会析构 → 内存泄漏!
}
解决方案:将其中一方改为 weak_ptr:
cpp
class B {
public:
std::weak_ptr<A> aPtr; // ✅ 使用 weak_ptr 打破循环
~B() { std::cout << "B destroyed\n"; }
};
⭐ make_unique / make_shared 和直接 new 有什么区别?
cpp
// 方式一:直接 new
std::shared_ptr<int> sp1(new int(42));
// 方式二:make_shared(推荐)
auto sp2 = std::make_shared<int>(42);
| 特性 | new + 构造 | make_shared / make_unique |
|---|---|---|
| 内存分配次数 | 2 次(对象 + 控制块) | 1 次(对象和控制块合并) |
| 异常安全 | 可能泄漏 | ✅ 安全 |
| 性能 | 稍差 | 更好 |
cpp
// 异常安全问题
Foo(std::shared_ptr<A>(new A()), std::shared_ptr<B>(new B()));
// 如果 new A() 成功,new B() 失败,A 的内存可能泄漏!
// 安全写法
Foo(std::make_shared<A>(), std::make_shared<B>());
什么是内存对齐?为什么需要?
CPU 访问对齐的内存更高效。编译器会在结构体成员之间插入填充字节:
cpp
struct A {
char a; // 1 字节
// 3 字节填充
int b; // 4 字节
char c; // 1 字节
// 3 字节填充
};
// sizeof(A) = 12(而不是 6)
// 调整成员顺序可以减少填充
struct B {
int b; // 4 字节
char a; // 1 字节
char c; // 1 字节
// 2 字节填充
};
// sizeof(B) = 8
🌈 建议:将成员按大小从大到小排列,减少内存浪费。
三、面向对象——类的基础
⭐ 面向对象的三大特性是什么?
| 特性 | 含义 | C++ 实现 |
|---|---|---|
| 封装 | 隐藏内部实现,暴露接口 | private / public / protected |
| 继承 | 子类复用父类的属性和行为 | class Derived : public Base |
| 多态 | 同一接口,不同行为 | 虚函数 virtual + override |
⭐ public、protected、private 的区别?
| 访问权限 | 类内部 | 子类 | 类外部 |
|---|---|---|---|
public | ✅ | ✅ | ✅ |
protected | ✅ | ✅ | ❌ |
private | ✅ | ❌ | ❌ |
cpp
class Base {
public:
int pubVar; // 任何地方可访问
protected:
int proVar; // 子类可访问
private:
int priVar; // 仅类内可访问
};
class Derived : public Base {
void Foo() {
pubVar = 1; // ✅
proVar = 2; // ✅
// priVar = 3; // ❌ 编译错误
}
};
⭐ 构造函数有哪些种类?
cpp
class MyClass {
int x_;
std::string name_;
public:
// 1. 默认构造函数
MyClass() : x_(0), name_("") {}
// 2. 参数构造函数
MyClass(int x, const std::string& name) : x_(x), name_(name) {}
// 3. 拷贝构造函数
MyClass(const MyClass& other) : x_(other.x_), name_(other.name_) {}
// 4. 移动构造函数(C++11)
MyClass(MyClass&& other) noexcept
: x_(other.x_), name_(std::move(other.name_)) {}
// 5. 委托构造函数(C++11)
MyClass(int x) : MyClass(x, "default") {} // 委托给参数构造
};
| 触发时机 | 调用的构造函数 |
|---|---|
MyClass a; | 默认构造 |
MyClass a(1, "hi"); | 参数构造 |
MyClass b = a; | 拷贝构造 |
MyClass b(a); | 拷贝构造 |
MyClass b = std::move(a); | 移动构造 |
MyClass b = MyClass(1, "hi"); | 参数构造(可能被优化,不调用移动) |
⭐ 为什么构造函数要使用初始化列表?
cpp
class Example {
const int id_; // const 成员
int& ref_; // 引用成员
std::string name_;
public:
// ✅ 初始化列表(推荐)
Example(int id, int& r, const std::string& name)
: id_(id), ref_(r), name_(name) {}
// ❌ 函数体内赋值(某些情况不可行)
// Example(int id, int& r, const std::string& name) {
// id_ = id; // ❌ const 成员不能赋值
// ref_ = r; // ❌ 引用不能重新绑定
// name_ = name; // 可以,但效率低(先默认构造,再赋值)
// }
};
必须使用初始化列表的场景:
| 场景 | 原因 |
|---|---|
const 成员 | const 初始化后不能修改 |
| 引用成员 | 引用必须在初始化时绑定 |
| 没有默认构造函数的成员 | 无法默认构造 |
| 基类构造函数 | 需要传参给基类 |
⭐ 什么是深拷贝和浅拷贝?
| 浅拷贝 | 深拷贝 | |
|---|---|---|
| 行为 | 复制指针的值(地址) | 复制指针指向的内容 |
| 风险 | 两个对象共享同一块内存 | 各自拥有独立内存 |
| 释放 | 可能重复释放(崩溃) | 安全 |
| 默认 | 编译器生成的拷贝构造就是浅拷贝 | 需要手动实现 |
cpp
class ShallowCopy {
int* data_;
public:
ShallowCopy(int val) : data_(new int(val)) {}
// 编译器默认生成的拷贝构造是浅拷贝:
// ShallowCopy(const ShallowCopy& other) : data_(other.data_) {}
// ⚠️ 两个对象的 data_ 指向同一块内存!
~ShallowCopy() { delete data_; } // 第二次析构时会重复释放!
};
class DeepCopy {
int* data_;
public:
DeepCopy(int val) : data_(new int(val)) {}
// 手动深拷贝
DeepCopy(const DeepCopy& other) : data_(new int(*other.data_)) {}
~DeepCopy() { delete data_; }
};
⭐ 什么是 explicit 关键字?
explicit 防止隐式类型转换:
cpp
class Foo {
public:
Foo(int x) {} // 允许隐式转换
};
class Bar {
public:
explicit Bar(int x) {} // 禁止隐式转换
};
Foo f1 = 42; // ✅ 隐式转换:42 → Foo(42)
// Bar b1 = 42; // ❌ 编译错误:explicit 禁止隐式转换
Bar b2(42); // ✅ 显式构造
Bar b3 = Bar(42); // ✅ 显式构造
🌈 建议:单参数构造函数都应该加
explicit,除非你确实需要隐式转换。
⭐ this 指针是什么?
this 是一个隐含的指针,指向当前对象自身:
cpp
class MyClass {
int x_;
public:
MyClass(int x) : x_(x) {}
// this 的用途1:区分成员变量和参数
void SetX(int x) { this->x_ = x; }
// this 的用途2:返回自身引用(链式调用)
MyClass& Add(int val) {
x_ += val;
return *this;
}
};
MyClass obj(0);
obj.Add(1).Add(2).Add(3); // 链式调用,x_ = 6
friend 友元的作用?
友元可以访问类的 private 和 protected 成员:
cpp
class MyClass {
int secret_ = 42;
friend void PrintSecret(const MyClass& obj); // 友元函数
friend class FriendClass; // 友元类
};
void PrintSecret(const MyClass& obj) {
std::cout << obj.secret_; // ✅ 可以访问 private 成员
}
class FriendClass {
void Access(const MyClass& obj) {
std::cout << obj.secret_; // ✅
}
};
⚠️ 友元破坏封装性,应谨慎使用。常见场景:运算符重载、测试类。
⭐ static 成员变量和成员函数的特点?
cpp
class Counter {
static int count_; // 静态成员变量(所有对象共享)
public:
Counter() { ++count_; }
~Counter() { --count_; }
static int GetCount() { // 静态成员函数
// this->xxx; // ❌ 静态函数没有 this 指针
return count_;
}
};
int Counter::count_ = 0; // 必须在类外初始化
Counter a, b, c;
Counter::GetCount(); // 3(通过类名调用)
a.GetCount(); // 也可以通过对象调用
| 特性 | 静态成员变量 | 静态成员函数 |
|---|---|---|
| 归属 | 类,而非对象 | 类,而非对象 |
| 共享 | 所有对象共享一份 | — |
this | — | ❌ 没有 this 指针 |
| 访问 | ClassName::var | ClassName::Func() |
| 初始化 | 类外初始化 | — |
| 只能访问 | — | 静态成员(不能访问非静态成员) |
四、面向对象——继承
⭐ 三种继承方式的区别?
| 基类成员 | public 继承 | protected 继承 | private 继承 |
|---|---|---|---|
public | → public | → protected | → private |
protected | → protected | → protected | → private |
private | 不可访问 | 不可访问 | 不可访问 |
🌈 实际开发中,99% 使用
public继承,表示 "is-a" 关系。
⭐ 继承中构造函数和析构函数的调用顺序?
构造顺序:基类 → 成员变量(按声明顺序)→ 派生类
析构顺序:派生类 → 成员变量(按声明逆序)→ 基类
cpp
class Base {
public:
Base() { std::cout << "Base ctor\n"; }
~Base() { std::cout << "Base dtor\n"; }
};
class Member {
public:
Member() { std::cout << "Member ctor\n"; }
~Member() { std::cout << "Member dtor\n"; }
};
class Derived : public Base {
Member m_;
public:
Derived() { std::cout << "Derived ctor\n"; }
~Derived() { std::cout << "Derived dtor\n"; }
};
// 构造输出:Base ctor → Member ctor → Derived ctor
// 析构输出:Derived dtor → Member dtor → Base dtor
⭐ 什么是多重继承?有什么问题?
cpp
class A { public: void Foo() {} };
class B { public: void Foo() {} };
class C : public A, public B {}; // 多重继承
C c;
// c.Foo(); // ❌ 二义性:A::Foo 还是 B::Foo?
c.A::Foo(); // ✅ 显式指定
c.B::Foo(); // ✅ 显式指定
⭐ 菱形继承和虚继承?
A
/ \
B C
\ /
D ← D 继承 B 和 C,都来自 A → A 有两份!
cpp
class A { public: int x_ = 0; };
class B : public A {};
class C : public A {};
class D : public B, public C {};
D d;
// d.x_; // ❌ 二义性:B::x_ 还是 C::x_?
d.B::x_ = 1; // ✅ 但 A 存在两份
// 解决方案:虚继承
class B : virtual public A {};
class C : virtual public A {};
class D : public B, public C {};
D d2;
d2.x_ = 1; // ✅ 只有一份 A 的成员
⚠️ 虚继承有额外开销(虚基类指针),应尽量避免菱形继承。
五、面向对象——多态
⭐ 什么是多态?C++ 中如何实现多态?
多态:同一接口,不同对象产生不同行为。
| 类型 | 实现方式 | 时机 | 示例 |
|---|---|---|---|
| 编译时多态 | 函数重载、模板 | 编译期 | Add(int) / Add(double) |
| 运行时多态 | 虚函数 + 继承 | 运行期 | 基类指针调用派生类方法 |
cpp
class Shape {
public:
virtual ~Shape() = default;
virtual double Area() const = 0; // 纯虚函数
};
class Circle : public Shape {
double r_;
public:
explicit Circle(double r) : r_(r) {}
double Area() const override { return 3.14159 * r_ * r_; }
};
class Rect : public Shape {
double w_, h_;
public:
Rect(double w, double h) : w_(w), h_(h) {}
double Area() const override { return w_ * h_; }
};
// 运行时多态
void PrintArea(const Shape& shape) {
std::cout << shape.Area() << "\n"; // 根据实际类型调用
}
Circle c(5);
Rect r(3, 4);
PrintArea(c); // 78.5398
PrintArea(r); // 12
⭐ 虚函数的实现原理(vtable)?
| 概念 | 说明 |
|---|---|
| 虚函数表(vtable) | 每个含虚函数的类都有一个 vtable,存放虚函数的地址 |
| 虚指针(vptr) | 每个对象内部有一个 vptr,指向所属类的 vtable |
| 调用过程 | 通过 vptr → vtable → 找到实际函数地址 → 调用 |
对象内存布局:
┌──────────┐
│ vptr │ → 指向 vtable
├──────────┤
│ 成员变量 │
└──────────┘
vtable:
┌──────────────────────┐
│ [0] → Derived::Func1 │
│ [1] → Base::Func2 │ (未重写,仍指向基类版本)
│ [2] → Derived::Func3 │
└──────────────────────┘
⚠️ 虚函数调用比普通函数多了一次间接寻址,有少量性能开销。
⭐ 为什么基类的析构函数必须是虚函数?
cpp
class Base {
public:
~Base() { std::cout << "Base dtor\n"; } // ❌ 非虚析构
// virtual ~Base() { std::cout << "Base dtor\n"; } // ✅ 虚析构
};
class Derived : public Base {
int* data_;
public:
Derived() : data_(new int[100]) {}
~Derived() {
delete[] data_;
std::cout << "Derived dtor\n";
}
};
Base* p = new Derived();
delete p;
// 非虚析构:只调用 Base::~Base(),Derived::~Derived() 不被调用 → 内存泄漏!
// 虚析构:先调用 Derived::~Derived(),再调用 Base::~Base() → 正确释放
⭐ 规则:只要一个类可能被继承,其析构函数就应该是
virtual的。
⭐ 纯虚函数和抽象类是什么?
cpp
class AbstractBase {
public:
virtual ~AbstractBase() = default;
virtual void DoWork() = 0; // 纯虚函数(= 0)
virtual void Log() { // 普通虚函数(有默认实现)
std::cout << "Logging...\n";
}
};
// AbstractBase obj; // ❌ 不能实例化抽象类
class Concrete : public AbstractBase {
public:
void DoWork() override { // 必须实现纯虚函数
std::cout << "Working!\n";
}
};
Concrete obj; // ✅ 可以实例化
| 概念 | 说明 |
|---|---|
| 纯虚函数 | virtual void Foo() = 0;,无实现,子类必须重写 |
| 抽象类 | 含有至少一个纯虚函数的类,不能实例化 |
| 接口类 | 所有成员函数都是纯虚函数的类(C++ 没有 interface 关键字) |
⭐ override 和 final 的作用?
cpp
class Base {
public:
virtual void Foo(int x) {}
virtual void Bar() {}
};
class Derived : public Base {
public:
// override:显式声明重写,编译器会检查签名
void Foo(int x) override {} // ✅
// void Foo(double x) override {} // ❌ 编译错误:签名不匹配
// final:禁止子类进一步重写
void Bar() final {}
};
class SubDerived : public Derived {
// void Bar() override {} // ❌ 编译错误:Bar 是 final 的
};
// final 也可以用于类
class FinalClass final {};
// class Sub : public FinalClass {}; // ❌ 编译错误:不能继承 final 类
🌈 建议:重写虚函数时始终加
override,让编译器帮你检查错误。
虚函数可以是内联的吗?
- 可以声明为
inline,但只有在编译期能确定调用对象类型时才会真正内联。 - 通过基类指针/引用调用时,不会内联(需要运行时查 vtable)。
cpp
class Base {
public:
inline virtual void Foo() { std::cout << "Base\n"; }
};
Base b;
b.Foo(); // 可能内联(编译期已知类型)
Base* p = new Derived();
p->Foo(); // 不会内联(需要运行时多态)
运算符重载的规则?
cpp
class Vector2D {
double x_, y_;
public:
Vector2D(double x, double y) : x_(x), y_(y) {}
// 成员函数重载
Vector2D operator+(const Vector2D& other) const {
return {x_ + other.x_, y_ + other.y_};
}
// 前置 ++
Vector2D& operator++() {
++x_; ++y_;
return *this;
}
// 后置 ++(用 int 占位区分)
Vector2D operator++(int) {
Vector2D old = *this;
++(*this);
return old;
}
// 下标运算符
double& operator[](int idx) {
return (idx == 0) ? x_ : y_;
}
// 输出运算符(必须是友元函数)
friend std::ostream& operator<<(std::ostream& os, const Vector2D& v) {
return os << "(" << v.x_ << ", " << v.y_ << ")";
}
};
不能重载的运算符::: . .* ?: sizeof typeid
💬 评论