好的,我来为你总结"类和对象(中)"关于取地址运算符重载,以及"类和对象(下)"的重点内容。我会尽量用通俗易懂的方式讲解,帮你打好基础。
第一部分:类和对象(中)--- 取地址运算符重载
这个部分其实非常简单,但在理解它之前,我们需要先搞懂一个更基础的概念:const成员函数。
1. const成员函数:让对象"只读"
想象一下,你有一个Date日期对象,它被声明为const Date d1(2024,7,5);(常量对象)。这意味着你不希望d1的日期被任何函数意外修改。
-
如何实现? 在成员函数的参数列表后面加上
const。 -
作用 :这个
const实际上修饰的是隐藏的this指针。它把this指针从Date* const this(一个不能改变指向的指针)变成了const Date* const this(一个不能改变指向,并且不能通过它修改对象内容的指针)。class Date {
public:
// 这个Print函数被const修饰,意味着它不能修改调用它的对象的成员变量
void Print() const { // const修饰的是隐藏的this指针
cout << _year << "-" << _month << "-" << _day << endl;
}
// 非const函数,可以修改成员变量
void SetDay(int day) {
_day = day; // 这是允许的
}
private:
int _year, _month, _day;
};int main() {
const Date d1(2024, 7, 5);
d1.Print(); // 正确:Print是const成员函数,可以被const对象调用
// d1.SetDay(10); // 错误!SetDay不是const成员函数,不能保证不修改d1
}
简单说 :如果一个对象是常量(const),那么它只能调用那些被声明为const的成员函数,因为这些函数承诺不会修改对象。
2. 取地址运算符重载:operator&
取地址运算符重载就是重载&这个符号,它分为两种:
-
普通取地址重载:用于普通对象。
-
const取地址重载:用于const对象。
class A {
public:
// 1. 普通对象的取地址重载
A* operator&() {
cout << "A* operator&()" << endl;
return this; // 正常情况下就是返回对象自己的地址(this)
}
// 2. const对象的取地址重载
const A* operator&() const {
cout << "const A* operator&() const" << endl;
return this;
}
};
核心要点:
-
99%的情况你不需要自己写 :编译器会自动生成这两个函数,而且生成的就完全够用了,直接返回对象地址(
this)。
特殊用途 :只有在非常特殊的场景下你才需要自己实现。比如,你不想让别人获取到这个对象的真实地址 (出于安全或设计目的),你就可以胡乱返回一个地址,或者返回nullptr。
class Secret {
public:
// 重载&,不让别人知道我的真实地址
Secret* operator&() {
return nullptr; // 或者 return (Secret*)0x12345678;
}
};
取地址重载小结 :它很简单,了解编译器会自动生成即可。记住const成员函数的概念,这个更重要。
第二部分:类和对象(下)--- 核心知识点精讲
这部分内容是关于类的更深层次的特性和技巧。
1. 再探构造函数:初始化列表
之前我们初始化成员变量是在构造函数体内赋值,像这样:
Date(int year, int month, int day) {
_year = year; // 这是赋值,不是初始化!
_month = month;
_day = day;
}
更专业、更正确的方式是使用初始化列表:
Date(int year, int month, int day)
: _year(year) // 这才是真正的初始化
, _month(month)
, _day(day)
{}
为什么必须用初始化列表?
有三种特殊的成员变量必须在初始化列表中初始化:
-
引用成员变量 (
int& _ref):引用必须在创建时绑定到一个实体。 -
const成员变量 (
const int _n):常量必须在创建时赋予初始值,之后不能修改。 -
没有默认构造函数的类类型成员 :如果一个类成员(比如
Time _t)它的类没有提供无参或全缺省的构造函数,你就必须通过初始化列表告诉编译器如何构造它。class Time {
public:
Time(int hour) { ... } // 只有带参构造,没有默认构造函数
};
class Date {
private:
int _year;
const int _n; // const成员
int& _ref; // 引用成员
Time _t; // 无默认构造的类成员
public:
// 错误写法:这些成员无法在函数体内"初始化"
// Date(int year, int n, int& ref) { ... }// 正确写法:使用初始化列表 Date(int year, int n, int& ref, int hour) : _year(year) , _n(n) // 初始化const成员 , _ref(ref) // 初始化引用成员,绑定到外部变量 , _t(hour) // 调用Time的带参构造函数 {}};
重要规则 :成员变量的初始化顺序只取决于它们在类中的声明顺序,与在初始化列表中的书写顺序无关。建议保持一致以避免混淆。
2. static成员:属于类,不属于某个对象
-
静态成员变量 :用
static修饰。它不属于任何一个对象,而是被所有同类对象共享。它存放在静态区。 必须在类外进行初始化 (在全局作用域),语法是类型 类名::变量名 = 值;。 -
静态成员函数 :用
static修饰。它没有隐藏的this指针,因此不能访问普通的成员变量(因为不知道访问哪个对象的),只能访问静态成员变量。 调用方式:可以通过对象调用(obj.func()),更推荐通过类名调用(ClassName::Func()),这直接表明了它是类的函数。class A {
private:
static int _scount; // 声明:用来统计创建了多少个A对象
public:
A() { ++_scount; } // 构造时计数+1
A(const A& a) { ++_scount; } // 拷贝构造时计数+1
~A() { --_scount; } // 析构时计数-1// 静态成员函数,没有this指针,用来获取计数 static int GetACount() { return _scount; }};
// 定义并初始化静态成员变量(必须在类外)
int A::_scount = 0;int main() {
A a1, a2;
A a3 = a1;
cout << A::GetACount() << endl; // 输出:3
{
A a4;
cout << A::GetACount() << endl; // 输出:4
} // a4析构
cout << A::GetACount() << endl; // 输出:3
}
3. 友元(friend):打破封装的黑客
友元提供了突破private和protected访问权限的方法。
-
友元函数:一个外部的全局函数,被声明为类的友元后,就可以直接访问这个类的私有和保护成员。
-
友元类 :类B被声明为类A的友元后,类B的所有成员函数都可以直接访问类A的私有和保护成员。
class A {
private:
int _secret = 10;
// 声明友元函数
friend void HackA(const A& a);
// 声明友元类
friend class B;
};
// 友元函数的实现
void HackA(const A& a) {
cout << "I know A's secret: " << a._secret << endl; // 直接访问私有成员,合法!
}
class B {
public:
void PeekA(const A& a) {
cout << "B knows A's secret: " << a._secret << endl; // 合法!
}
};
注意:
- 友元关系是单向的(A把B当朋友,B不一定要把A当朋友)。
- 友元关系不能传递(A是B的朋友,B是C的朋友,但A不是C的朋友)。
- 慎用友元 :它破坏了封装性,增加了耦合度。但在重载
<<(输出)和>>(输入)运算符时非常有用。
4. 内部类:类中类
一个类可以定义在另一个类的内部,它就是内部类。它像一个被封装在外部类里面的独立类。
-
特性 : 内部类天生就是外部类的友元 ,可以访问外部类的私有静态成员、以及通过对象访问私有成员。 内部类不占用外部类对象的大小 ,它是独立的。 受外部类的类域和访问限定符(
public/private/protected)限制。class Outer {
private:
static int _static_val;
int _private_val;
public:
class Inner { // Inner是Outer的友元
public:
void AccessOuter(const Outer& o) {
cout << _static_val << endl; // 可以直接访问外部类的静态私有成员
cout << o._private_val << endl; // 可以通过外部类对象访问其私有成员
}
};
};
int Outer::_static_val = 100;
5. 匿名对象:用完即焚
匿名对象的生命周期只有它所在的那一行,声明方式为类名(参数)。
class Solution {
public:
int Sum_Solution(int n) {
return n;
}
};
int main() {
// 有名对象
Solution s;
s.Sum_Solution(10);
// 匿名对象:不需要名字,直接使用,这一行结束就销毁
Solution().Sum_Solution(10);
}
用途:当你只需要临时用一个对象来调用某个方法,之后不再需要它时,使用匿名对象非常方便。
6. 编译器优化:聪明的编译器
现代C++编译器会在不改变程序逻辑的前提下,尽可能地减少对象拷贝,提升效率。
常见优化场景:
-
连续构造+拷贝构造 -> 优化为直接构造。
-
传值返回时,局部对象拷贝到临时对象,再拷贝到接收对象 -> 优化为直接构造接收对象。
A GetA() {
return A(10); // 理论上:构造A(10) -> 拷贝给临时对象 -> 拷贝给main中的aa
} // 实际上(优化后):直接在GetA函数里为main中的aa分配空间并构造
int main() {
A aa = GetA(); // 优化后,可能只有一次构造操作
}
注意:优化因编译器而异,但理解这些优化能帮助你写出更高效的代码。
总结与联系
| 特性 | 核心思想 | 为什么重要? |
|---|---|---|
| 取地址重载/const成员函数 | 控制对象地址的获取和对象的"只读"性。 | 完善对对象行为的控制,是理解C++封装和常量正确性的基础。 |
| 初始化列表 | 真正初始化成员的地方,尤其是特殊成员。 | 正确的初始化是避免未定义行为的关键,是编写稳健类的基石。 |
| static成员 | 属于类本身的变量和函数,被所有对象共享。 | 实现对象间通信、管理类级别资源(如计数器)的必备工具。 |
| 友元 | 授予特定函数或类访问私有成员的"特权"。 | 在需要突破封装时(如输入输出流重载)提供灵活性。 |
| 内部类 | 将紧密关联的类封装在一起,增强代码内聚性。 | 用于设计更清晰、更模块化的代码结构。 |
| 匿名对象 | 临时使用的对象,生命周期极短。 | 简化代码,避免创建不必要的命名对象。 |
| 编译器优化 | 编译器自动减少不必要的对象拷贝。 | 理解编译器行为,有助于写出性能更高的C++代码。 |
内部类 | 将紧密关联的类封装在一起,增强代码内聚性。 | 用于设计更清晰、更模块化的代码结构。 |
| 匿名对象 | 临时使用的对象,生命周期极短。 | 简化代码,避免创建不必要的命名对象。 |
| 编译器优化 | 编译器自动减少不必要的对象拷贝。 | 理解编译器行为,有助于写出性能更高的C++代码。 |
希望这份总结能帮助你清晰地理解这些概念!这些知识是学习后面更复杂的C++特性(如模板、智能指针等)的重要基础。如果还有不清楚的地方,可以随时再问。