C++ 类与对象(下篇)笔记整理
C++ 类与对象(下篇)笔记整理
一、再谈构造函数
构造函数是类的核心成员函数,用于对象的初始化。本节从构造函数体赋值、初始化列表、explicit 关键字三个维度深入解析。
1.1 构造函数体赋值
概念
构造函数体赋值是通过在构造函数内部,使用=
对成员变量进行赋值的初始化方式。其本质是:先对成员变量进行默认初始化(如 int 默认随机值、自定义类型调用默认构造),再执行赋值操作。
局限性
- 效率较低:多了 “默认初始化→赋值” 的冗余步骤,尤其对自定义类型成员,会额外触发默认构造和析构。
- 无法初始化特殊成员:
const
修饰的成员、引用类型成员、无默认构造函数的自定义类型成员,无法通过构造函数体赋值初始化(这些成员必须在 “定义时” 完成初始化,而构造函数体执行时成员已默认初始化)。
示例(对比初始化列表)
cpp
运行
class Date {
private:int _year;int _month;int _day;
public:// 构造函数体赋值:先默认初始化_year/_month/_day,再赋值Date(int year = 0, int month = 1, int day = 1) {_year = year; // 赋值操作,非初始化_month = month;_day = day;}
};
1.2 初始化列表(核心)
1.2.1 概念与语法
初始化列表是 C++ 提供的直接初始化成员变量的语法,位于构造函数参数列表后、函数体前,用:
开头,成员初始化项用,
分隔。其本质是:成员变量的 “定义 + 初始化” 区域,成员在此处直接完成初始化,无默认初始化步骤。
1.2.2 基础语法示例(Date 类)
cpp
运行
class Date {
private:int _year; // 成员声明(仅占坑,无内存)int _month;int _day;
public:// 初始化列表:直接初始化成员变量(定义+赋值,开辟内存)Date(int year = 0, int month = 1, int day = 1): _year(year) // _year直接用year初始化, _month(month), _day(day){} // 函数体可空(无额外逻辑时)
};// 调用:创建对象时,通过初始化列表完成成员初始化
int main() {Date d1(2025, 10, 14); // _year=2025, _month=10, _day=14Date d2; // 使用默认参数:_year=0, _month=1, _day=1return 0;
}
1.2.3 必须使用初始化列表的 4 种场景
以下成员必须在定义时完成初始化,只能通过初始化列表实现,无法用构造函数体赋值:
场景 1:const 修饰的成员变量
const
变量的特性是 “初始化后不可修改”,且必须在定义时初始化。
cpp
运行
class Test {
private:const int _n; // const成员,必须定义时初始化
public:// 必须用初始化列表初始化_nTest(int n) : _n(n) {} // 正确:_n在初始化列表定义并赋值// Test(int n) { _n = n; } // 错误:_n已默认初始化(随机值),赋值时冲突
};
场景 2:引用类型成员
引用的特性是 “必须在定义时绑定到一个对象”,且绑定后不可更改指向。
cpp
运行
class Test {
private:int& _ref; // 引用成员,必须定义时绑定
public:// 必须用初始化列表绑定_ref到参数xTest(int& x) : _ref(x) {} // 正确:_ref绑定到x// Test(int& x) { _ref = x; } // 错误:_ref已默认初始化(悬空引用),赋值无效
};
场景 3:无默认构造函数的自定义类型成员
若成员是自定义类型(如类 A),且该类没有默认构造函数(即必须传参才能构造),则必须在初始化列表中给该成员传递构造参数。
cpp
运行
// 类A:无默认构造函数(只有带参构造)
class A {
private:int _a;
public:A(int a) : _a(a) {} // 带参构造,无默认构造
};// 类B:包含A类型成员,必须用初始化列表初始化_aobj
class B {
private:A _aobj; // A无默认构造,必须传参初始化
public:// 初始化列表:给_aobj传递构造参数1B() : _aobj(1) {} // 正确:_aobj用A(1)初始化// B() { _aobj = A(1); } // 错误:_aobj需先默认构造(无默认构造,编译报错)
};
场景 4:派生类初始化基类(后续继承内容,提前补充)
若基类的构造函数需要参数,则派生类必须通过初始化列表将参数传递给基类构造函数,确保基类先于派生类初始化。
cpp
运行
// 基类Base:带参构造
class Base {
private:int _baseVal;
public:Base(int val) : _baseVal(val) {}
};// 派生类Derived:继承Base
class Derived : public Base {
private:int _derivedVal;
public:// 初始化列表:先给基类Base传参,再初始化派生类成员Derived(int baseVal, int derivedVal): Base(baseVal) // 必须在此处初始化基类, _derivedVal(derivedVal){}
};
1.2.4 性能优势
对比构造函数体赋值,初始化列表减少了 “默认初始化” 步骤,直接完成成员初始化:
- 对内置类型(int、double 等):性能差异微小,但逻辑更直接。
- 对自定义类型(如 string、Date 等):避免了 “默认构造→赋值” 的冗余,减少构造和析构开销(例如 string 默认构造会分配空内存,赋值时需释放旧内存再分配新内存,而初始化列表直接用参数构造,无冗余操作)。
1.2.5 成员初始化顺序(关键注意点)
成员在初始化列表中的初始化顺序,不取决于列表中的书写顺序,而取决于成员在类中的声明顺序。若成员初始化存在依赖关系,必须保证声明顺序正确,否则会导致逻辑错误。
示例 1:正确声明顺序
cpp
运行
class Test {
private:int x; // 声明顺序1:先初始化xint y; // 声明顺序2:后初始化y
public:// 列表中y写在x前,但实际初始化顺序是x→y(按声明顺序)Test() : y(2), x(1) {}
};
示例 2:错误声明顺序(依赖冲突)
cpp
运行
class Test {
private:int x; // 声明顺序1:先初始化xint y; // 声明顺序2:后初始化y
public:// 错误:y未初始化时,用y初始化x(x先初始化,y此时是随机值)Test() : x(y), y(2) {}
};
1.3 explicit 关键字
概念
explicit
用于修饰单参数构造函数(或除第一个参数外其余参数均有默认值的构造函数),作用是禁止隐式类型转换(即禁止通过 “赋值语法” 创建对象)。
隐式类型转换的原理(未用 explicit 时)
若 Date 类有单参数构造函数,编译器会允许 “Date d = 2025;
” 这种语法,本质是:
- 隐式创建临时对象:
Date tmp(2025)
(用 2025 调用单参数构造)。 - 用临时对象拷贝构造 d:
Date d(tmp)
。 - 编译器优化:省略临时对象,直接用 2025 构造 d(即
Date d(2025)
)。
示例(未用 explicit):
cpp
运行
class Date {
private:int _year;
public:// 单参数构造函数:未用explicit,允许隐式转换Date(int year) : _year(year) {}
};int main() {Date d1(2025); // 显式构造:正确Date d2 = 2025; // 隐式转换:允许(编译器优化为直接构造)return 0;
}
explicit 的作用(禁止隐式转换)
cpp
运行
class Date {
private:int _year;
public:// 用explicit修饰:禁止隐式类型转换explicit Date(int year) : _year(year) {}
};int main() {Date d1(2025); // 显式构造:正确// Date d2 = 2025; // 错误:explicit禁止隐式转换,编译报错return 0;
}
补充:explicit 与多参数构造函数(C++11 后)
若构造函数除第一个参数外,其余参数均有默认值,也属于 “可隐式转换” 的场景,需用explicit
禁止:
cpp
运行
class Date {
private:int _year, _month, _day;
public:// 多参数但后两个有默认值:可隐式转换(如Date d=2025)explicit Date(int year, int month=1, int day=1): _year(year), _month(month), _day(day){}
};int main() {// Date d = 2025; // 错误:explicit禁止Date d(2025, 10, 14); // 正确return 0;
}
二、static 成员(类级别的成员)
static
修饰的成员(变量 / 函数)属于整个类,而非某个对象,是 “类级别的成员”,所有对象共享该成员。
2.1 静态成员变量
2.1.1 概念与特性
- 存储位置:不在对象内存中,而是在静态区(程序运行期间一直存在,程序结束后释放)。
- 共享性:所有类对象共享同一个静态成员变量,修改一个对象的静态成员,会影响所有对象。
- 声明与定义:
- 声明:在类内用
static
修饰(如static int n;
),仅占坑,不分配内存。 - 定义:必须在类外定义(如
int A::n = 0;
),否则链接时会报错(未分配内存)。
- 声明:在类内用
- 访问权限:受
public/private/protected
控制(同普通成员)。
2.1.2 示例(统计对象个数)
cpp
运行
class A {
private:// 静态成员变量声明:统计A类对象总数(所有对象共享)static int _totalCount;
public:// 默认构造:创建对象时,总数+1A() { ++_totalCount; }// 拷贝构造:创建新对象时,总数+1A(const A& a) { ++_totalCount; }// 静态成员函数:获取总数(后续讲解)static int getTotalCount() { return _totalCount; }
};// 静态成员变量类外定义并初始化(必须!否则链接报错)
int A::n = 0;// 测试
int main() {A a1;A a2(a1);// 访问静态成员函数:获取对象总数(2个)cout << "Total objects: " << A::getTotalCount() << endl; // 输出2return 0;
}
2.1.3 类外访问控制
静态成员变量的类外访问受权限控制:
public
:可通过 “类名::成员名
” 直接访问和修改(无需对象)。cpp
运行
class Sum { public:static int i; // public静态成员 }; int Sum::i = 0; // 类外定义int main() {Sum::i = 10; // 合法:public权限,类外可修改cout << Sum::i << endl; // 输出10return 0; }
private/protected
:类外无法直接访问(即使通过 “类名::成员名
” 也会编译报错),只能通过类内的公有成员函数(如静态成员函数)访问。cpp
运行
class Sum { private:static int i; // private静态成员 public:static int getI() { return i; } // 公有静态函数访问private成员 }; int Sum::i = 0; // 类外定义(合法:定义不受访问权限限制)int main() {// Sum::i = 10; // 错误:private权限,类外无法修改cout << Sum::getI() << endl; // 合法:通过公有函数访问,输出0return 0; }
2.2 静态成员函数
2.2.1 概念与特性
- 类级别函数:属于整个类,无需创建对象即可调用(通过 “
类名::函数名
” 调用)。 - 无
this
指针:因不依赖具体对象,静态成员函数内部没有this
指针(this
指向当前对象)。 - 访问限制:
- 只能访问静态成员(静态变量 / 静态函数):非静态成员属于对象,需
this
指针访问,而静态函数无this
。 - 不能访问非静态成员:若需访问,必须显式传递对象的指针 / 引用。
- 只能访问静态成员(静态变量 / 静态函数):非静态成员属于对象,需
2.2.2 示例
cpp
运行
class A {
private:int _a; // 非静态成员(属于对象)static int _n; // 静态成员(属于类)
public:A(int a) : _a(a) {}// 静态成员函数:只能访问静态成员_nstatic int getN() { return _n; }// 静态成员函数:显式传递对象,访问非静态成员_astatic int getA(A& obj) { return obj._a; } // 正确:通过对象引用访问
};// 静态成员变量类外定义
int A::n = 100;// 测试
int main() {A a(20);// 调用静态函数:无需对象,类名直接调用cout << "A::_n = " << A::getN() << endl; // 输出100// 调用静态函数:传递对象,访问非静态成员cout << "a._a = " << A::getA(a) << endl; // 输出20return 0;
}
2.3 常见问题
问题 1:静态成员函数可以调用非静态成员函数吗?
不能直接调用。原因:
- 非静态成员函数隐含
this
指针,需要绑定具体对象才能调用。 - 静态成员函数无
this
指针,无法确定调用哪个对象的非静态成员函数。 - 解决方案:显式传递对象的指针 / 引用,让非静态函数绑定对象。
cpp
运行
class A { private:int _a;void nonStaticFunc() { cout << _a << endl; } // 非静态函数 public:A(int a) : _a(a) {}// 静态函数:显式传递对象,调用非静态函数static void staticFunc(A& obj) {obj.nonStaticFunc(); // 正确:通过对象调用} };
问题 2:非静态成员函数可以调用静态成员函数吗?
可以直接调用。原因:
- 非静态成员函数有
this
指针(绑定对象),但静态成员函数属于类,不依赖对象。 - 非静态函数调用静态函数时,无需额外传递对象,直接调用即可(相当于调用 “类共有的功能”)。
cpp
运行
class A { private:static int _n;static void staticFunc() { cout << _n << endl; } // 静态函数 public:int _a;// 非静态函数:直接调用静态函数void nonStaticFunc() {staticFunc(); // 正确:无需类名,类内可直接调用} }; int A::n = 100;
三、C++11 成员初始化新玩法(类内缺省值)
C++11 允许在类内声明非静态成员变量时,直接设置缺省值,简化默认初始化逻辑(无需在默认构造函数中重复写默认值)。
3.1 特性与规则
- 适用范围:仅非静态成员变量(静态成员变量需类外定义,不能类内缺省)。
- 覆盖规则:若构造函数的初始化列表显式初始化该成员,则覆盖类内缺省值;若未显式初始化,则使用类内缺省值。
- 本质:类内缺省值是 “默认初始化的备选值”,仅在成员未被显式初始化时生效。
3.2 示例
cpp
运行
class Date {
private:// C++11:非静态成员类内缺省值(声明时设置)int _year = 0; // 默认年:0int _month = 1; // 默认月:1int _day = 1; // 默认日:1
public:// 1. 默认构造:未显式初始化成员,使用类内缺省值Date() {}// 2. 带参构造:初始化列表显式赋值,覆盖缺省值Date(int year, int month, int day): _year(year) // 覆盖缺省值0, _month(month) // 覆盖缺省值1, _day(day) // 覆盖缺省值1{}// 3. 部分显式初始化:未显式的成员用缺省值Date(int year): _year(year) // 显式初始化_year{} // _month=1(缺省),_day=1(缺省)// 打印日期void Print() {cout << _year << "/" << _month << "/" << _day << endl;}
};// 测试
int main() {Date d1; // 调用默认构造:0/1/1Date d2(2025); // 部分显式:2025/1/1Date d3(2025,10,14); // 全显式:2025/10/14d1.Print();d2.Print();d3.Print();return 0;
}
3.3 补充:与静态成员的区别
静态成员变量不能类内缺省初始化,必须在类外定义时赋值:
cpp
运行
class A {
public:// static int _n = 0; // 错误:静态成员不能类内缺省static int _n; // 声明
};
int A::n = 0; // 类外定义并初始化(正确)
四、友元(突破封装的特殊机制)
友元是 C++ 提供的 “打破封装” 的机制,允许外部的函数 / 类直接访问当前类的private/protected
成员。但友元会增加代码耦合度,破坏封装性,需谨慎使用。
4.1 友元函数
4.1.1 概念与特性
- 定义:在类内用
friend
声明的全局函数(或其他类的成员函数),不是当前类的成员函数。 - 权限:可直接访问当前类的
private/protected
成员(无需通过公有接口)。 - 声明位置:类内的
public/private/protected
区域均可(不影响友元权限)。 - 传递性:友元关系不可传递(A 是 B 的友元,B 是 C 的友元,A 不是 C 的友元)。
4.1.2 典型应用:重载operator<<
(输出运算符)
若将operator<<
重载为类的成员函数,会导致左操作数必须是类对象(因成员函数隐含this
指针),不符合 “cout << 对象
” 的常规用法。此时需将operator<<
重载为全局函数,并声明为类的友元。
示例:
cpp
运行
#include <iostream>
using namespace std;class Date {
private:int _year = 0;int _month = 1;int _day = 1;
public:Date(int year=0, int month=1, int day=1): _year(year), _month(month), _day(day){}// 友元函数声明:允许operator<<访问private成员friend ostream& operator<<(ostream& out, const Date& d);
};// 友元函数定义(全局函数):重载operator<<
ostream& operator<<(ostream& out, const Date& d) {// 直接访问Date的private成员:_year/_month/_dayout << d._year << "/" << d._month << "/" << d._day;return out; // 返回out,支持连续输出(如cout << d1 << d2)
}// 测试
int main() {Date d(2025, 10, 14);cout << "Date: " << d << endl; // 输出:Date: 2025/10/14Date d2(2025, 12, 31);cout << d << " to " << d2 << endl; // 连续输出:2025/10/14 to 2025/12/31return 0;
}
4.1.3 补充:友元函数与成员函数的区别
特性 | 友元函数 | 成员函数 |
---|---|---|
是否属于类 | 否(全局函数) | 是 |
是否有this 指针 | 无 | 有(非静态成员函数) |
调用方式 | 直接调用(如func(d) ) | 对象调用(如d.func() ) |
访问成员的方式 | 需显式传递对象 | 隐式通过this 访问 |
4.2 友元类
4.2.1 概念与特性
- 定义:若类 A 是类 B 的友元,则类 A 的所有成员函数(包括普通和静态成员函数)均可直接访问类 B 的
private/protected
成员。 - 单向性:A 是 B 的友元,不代表 B 是 A 的友元(B 无法访问 A 的私有成员)。
- 不可传递:A 是 B 的友元,B 是 C 的友元,不代表 A 是 C 的友元。
- 不可继承:友元关系不能被派生类继承(A 是 B 的友元,B 的派生类 C 不是 A 的友元)。
4.2.2 示例
cpp
运行
class B {
private:int _bVal = 100;// 友元类声明:A是B的友元,A的所有成员函数可访问B的private成员friend class A;
};class A {
public:// A的成员函数:访问B的private成员_bValvoid printBVal(B& b) {cout << "B::_bVal = " << b._bVal << endl; // 正确:友元类权限}
};// 测试
int main() {A a;B b;a.printBVal(b); // 输出:B::_bVal = 100return 0;
}
4.2.3 注意事项
- 友元类会显著增加类间耦合度,仅在 “两个类高度关联且必须共享细节” 时使用(如容器类与迭代器类)。
- 避免滥用:优先通过公有接口(如
Get/Set
函数)访问成员,而非直接使用友元。
五、内部类(类内定义的类)
内部类是在一个类的内部定义的另一个类,属于 “嵌套类”,本质是独立的类,与外部类仅存在 “语法上的嵌套关系”。
5.1 概念与特性
- 独立性:内部类是独立的类,编译时会生成单独的类结构,
sizeof(外部类)
不包含内部类的大小(内部类的对象不依赖外部类对象)。 - 友元关系:内部类默认是外部类的友元(内部类的成员函数可访问外部类的
private/protected
成员,包括静态和非静态成员)。 - 访问规则:
- 内部类访问外部类成员:
- 静态成员:无需外部类对象,直接通过 “
外部类名::静态成员
” 访问。 - 非静态成员:需显式传递外部类对象(通过指针 / 引用)。
- 静态成员:无需外部类对象,直接通过 “
- 外部类访问内部类成员:需通过内部类对象(内部类不是外部类的友元,外部类无法直接访问内部类的私有成员)。
- 内部类访问外部类成员:
- 作用域:内部类的作用域在外部类内,创建内部类对象时需指定 “
外部类名::内部类名
”(类外创建时)。
5.2 示例
cpp
运行
class Outer {
private:int _outerNonStatic = 20; // 外部类非静态成员static int _outerStatic = 100; // 外部类静态成员
public:// 内部类定义(作用域在Outer内)class Inner {private:int _innerVal = 5; // 内部类私有成员public:// 内部类成员函数:访问外部类成员void accessOuter(Outer& out) {// 访问外部类静态成员:无需对象cout << "Outer::_outerStatic = " << Outer::_outerStatic << endl;// 访问外部类非静态成员:需外部类对象cout << "Outer::_outerNonStatic = " << out._outerNonStatic << endl;}// 内部类成员函数:访问自身成员int getInnerVal() { return _innerVal; }};
};// 外部类静态成员类外定义(若未类内初始化)
// int Outer::_outerStatic = 100;// 测试
int main() {// 1. 创建内部类对象:需指定外部类域(Outer::Inner)Outer::Inner innerObj;// 2. 内部类访问外部类成员Outer outerObj;innerObj.accessOuter(outerObj); // 输出:100 和 20// 3. 外部类访问内部类成员:需通过内部类对象(调用公有接口)cout << "Inner::_innerVal = " << innerObj.getInnerVal() << endl; // 输出5// 4. 外部类大小:仅包含_outerNonStatic(4字节),不包含Innercout << "sizeof(Outer) = " << sizeof(Outer) << endl; // 输出4return 0;
}
5.3 补充:内部类与友元的区别
特性 | 内部类 | 友元类 |
---|---|---|
关系性质 | 语法嵌套(独立类) | 权限共享(独立类) |
友元关系 | 内部类默认是外部类的友元 | 需显式声明友元关系 |
外部类访问权限 | 需通过内部类对象(无友元) | 需显式声明才有权限 |
作用域 | 内部类作用域在外部类内 | 友元类作用域独立 |
六、练习题(综合应用)
题目:设计一个计数器类Counter
,统计对象的 “总创建次数” 和 “当前存活次数”
要求:
- 总创建次数:所有对象(包括默认构造和拷贝构造创建的)的总数。
- 当前存活次数:当前未被析构的对象数(创建时 + 1,析构时 - 1)。
- 提供静态成员函数获取两个次数。
实现代码与注释
cpp
运行
#include <iostream>
using namespace std;class Counter {
private:// 静态成员变量:总创建次数(所有对象共享)static int _totalCreated;// 静态成员变量:当前存活次数(所有对象共享)static int _currentAlive;public:// 1. 默认构造:创建对象,总次数+1,存活次数+1Counter() {++_totalCreated;++_currentAlive;cout << "Counter default constructed. ";cout << "Total: " << _totalCreated << ", Alive: " << _currentAlive << endl;}// 2. 拷贝构造:创建新对象,总次数+1,存活次数+1Counter(const Counter&) {++_totalCreated;++_currentAlive;cout << "Counter copy constructed. ";cout << "Total: " << _totalCreated << ", Alive: " << _currentAlive << endl;}// 3. 析构函数:销毁对象,存活次数-1~Counter() {--_currentAlive;cout << "Counter destructed. ";cout << "Total: " << _totalCreated << ", Alive: " << _currentAlive << endl;}// 4. 静态成员函数:获取总创建次数static int getTotalCreated() {return _totalCreated;}// 5. 静态成员函数:获取当前存活次数static int getCurrentAlive() {return _currentAlive;}
};// 静态成员变量类外定义并初始化(必须!)
int Counter::_totalCreated = 0;
int Counter::_currentAlive = 0;// 测试逻辑
int main() {cout << "=== Main start ===" << endl;// 创建对象c1(默认构造)Counter c1;{// 局部作用域:创建c2(拷贝构造c1)Counter c2(c1);cout << "=== Inner scope end ===" << endl;} // c2析构(局部作用域结束)// 创建c3(默认构造)Counter c3;cout << "=== Main end ===" << endl;// c1、c3析构(main函数结束)return 0;
}
输出结果与分析
plaintext
=== Main start ===
Counter default constructed. Total: 1, Alive: 1
Counter copy constructed. Total: 2, Alive: 2
=== Inner scope end ===
Counter destructed. Total: 2, Alive: 1
Counter default constructed. Total: 3, Alive: 2
=== Main end ===
Counter destructed. Total: 3, Alive: 1
Counter destructed. Total: 3, Alive: 0
- 总创建次数最终为 3(c1、c2、c3)。
- 存活次数随对象创建 / 析构动态变化,main 结束后为 0(所有对象析构)。
七、再次理解封装
封装是面向对象三大特性(封装、继承、多态)的基础,核心是 “隐藏内部细节,暴露必要接口”。
7.1 封装的本质
- 数据与行为绑定:将对象的 “属性(成员变量)” 和 “行为(成员函数)” 封装在一个类中,模拟现实世界中 “事物的属性与功能一体” 的特点(如 “日期” 的属性是年 / 月 / 日,行为是打印、判断合法性)。
- 访问控制:通过
public/private/protected
控制成员的访问权限:private
:隐藏内部细节(如成员变量、辅助函数),仅类内可访问,防止外部随意修改。public
:暴露必要接口(如构造函数、Print
、SetDate
),外部通过接口与类交互,无需关心内部实现。protected
:用于继承(后续讲解),派生类可访问,外部不可访问。
7.2 封装的意义
7.2.1 工程维护角度(低耦合、高内聚)
- 低耦合:类的内部逻辑修改时,只要对外接口(
public
成员)不变,外部代码无需修改(如 Date 类内部增加 “判断日期合法性” 的逻辑,外部调用Date(2025,13,1)
时会报错,但调用方式不变)。 - 高内聚:类的功能集中(如 Date 类仅处理日期相关逻辑),避免功能分散导致的维护困难。
7.2.2 数据安全角度
- 防止非法修改:通过
private
隐藏成员变量,外部无法直接修改,需通过公有接口(如SetYear(int year)
)修改,接口中可增加合法性校验(如year
不能小于 0)。cpp
运行
class Date { private:int _year;// 私有辅助函数:判断年份合法性bool isYearValid(int year) { return year >= 0; } public:// 公有接口:设置年份,带合法性校验void SetYear(int year) {if (isYearValid(year)) {_year = year;} else {cout << "Invalid year!" << endl;}} };
八、再次理解面向对象
面向对象(OOP)的核心是 “以对象为中心,模拟现实世界”,通过类与对象的机制,将复杂问题拆解为多个 “对象间的交互”。
8.1 类与对象的关系
- 类是 “模板”:类是对一类事物的抽象描述(如 “日期” 类描述了所有日期的共同属性和行为),无具体数据,不占用内存。
- 对象是 “实例”:对象是类的具体实例(如 “2025 年 10 月 14 日” 是 Date 类的一个对象),有具体数据,占用内存。
- 类比:类是 “汽车设计图”,对象是 “根据设计图造的具体汽车”。
8.2 面向对象三大特性的关系
- 封装是基础:通过封装隐藏细节、暴露接口,为继承和多态提供安全的基础。
- 继承是复用:基于现有类(基类)创建新类(派生类),复用基类的代码,减少冗余(如基于 Date 类创建 DateTime 类,增加 “时 / 分 / 秒” 属性)。
- 多态是接口复用:不同类的对象通过统一的接口(如虚函数)表现出不同的行为(如 Date 和 DateTime 都有
Print
接口,但打印格式不同)。
8.3 面向对象的优势
- 可维护性:封装使代码模块化,修改一个类的内部逻辑不影响其他类。
- 可扩展性:通过继承和多态,可轻松添加新功能(如增加新的日期格式,只需派生一个新类,无需修改原有代码)。
- 可复用性:类可以被多个模块复用(如 Date 类可用于日历、闹钟等模块)。
九、补充疏漏点总结
-
成员声明与定义的区别:
- 声明:类内声明成员(如
int _year;
),仅告诉编译器 “成员存在”,不分配内存。 - 定义:对象创建时(通过初始化列表),成员才分配内存并初始化(如
Date d(2025);
时,_year
分配内存并赋值 2025)。
- 声明:类内声明成员(如
-
对象的内存布局:
- 非静态成员变量:存储在对象内存中,
sizeof(对象)
= 所有非静态成员变量的大小(需考虑内存对齐)。 - 静态成员变量 / 函数:存储在静态区,不占用对象内存。
- 成员函数:存储在代码区,所有对象共享同一套成员函数(通过
this
指针区分不同对象)。
- 非静态成员变量:存储在对象内存中,
-
初始化列表与类内缺省值的优先级:
- 初始化列表显式初始化 → 类内缺省值 → 默认初始化(内置类型随机值,自定义类型调用默认构造)。
-
友元的声明位置:
- 友元函数 / 类的声明可放在类内的任何区域(
public/private/protected
),不影响其友元权限。
- 友元函数 / 类的声明可放在类内的任何区域(