👋 欢迎阅读

🎯 欢迎来到「C++ 类和对象(进阶)」深入讲解! 本文将带你从"构造函数体的局限性"出发,逐步深入理解 初始化列表、static 成员、友元、内部类 ,并最终再次审视类与对象的本质,让你对 C++ 的面向对象设计有更全局的认知。
在开始之前,建议你先:
了解背景知识 :在掌握了类的默认成员函数(构造、析构、拷贝等)之后,我们还需要了解一些进阶特性,它们能帮助我们更灵活地控制对象的初始化、共享数据、突破封装限制、以及设计更清晰的类结构。
明确学习目标 :掌握以下五个核心知识点------
① 初始化列表 :掌握初始化列表的语法形式(在构造函数体前以冒号开头,用逗号分隔),理解它必须用于 const 成员和引用成员 的初始化,并区分"初始化"与"赋值"的本质差异;
② static 成员 :理解静态成员变量属于类而不属于对象 ,需在类外单独定义并初始化;掌握静态成员函数的特性(无 this 指针,只能访问静态成员),以及它的典型应用场景(如统计对象个数、实现单例模式);
③ 友元(friend) :了解友元函数和友元类的声明方式,理解它突破了类的封装性 ,能够访问类的私有成员,但同时也要明确它破坏了封装 ,应谨慎使用;
④ 内部类(嵌套类) :掌握将一个类定义在另一个类内部的方式,理解内部类与外部类相互独立 (仅受作用域限制),它可以直接访问外部类的静态成员,但访问非静态成员需通过外部类对象;
⑤ 再次理解类与对象 :回顾类的本质------类是一种自定义类型 ,用于描述某一类实体(对象)所拥有的属性和方法;对象则是该类类型的一个具体实例 ,在内存中占有独立空间。深刻理解"类是图纸,对象是房子"的比喻,并意识到类是抽象的,对象是具体的,类是对象的蓝图,对象是类的实体化。
一.在谈构造函数
1.1 构造函数体赋值
在创建对象时,编译器通过调用构造函数,给对象中各个成员变量一个合适的初始值
cpp
class Date
{
public:
Date(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
private:
int _year;
int _month;
int _day;
};
虽然上述构造函数调用之后,对象中已经有了一个初始值,但是不能将其称为对对象中成员变量的初始化 , 构造函数体中的语句只能将其称为赋初值 ,而不能称作初始化。因为初始化只能初始化一次,而构造函数体 内可以多次赋值。
1.2 初始化列表
初始化列表:以一个冒号开始 ,接着是一个以逗号分隔的数据成员列表 ,每个**"** 成员变量 " 后面跟一个放在括 号中的初始值或表达式。
cpp
class Date
{
public:
Date(int year, int month, int day)
: _year(year)
, _month(month)
, _day(day)
{}
private:
int _year;
int _month;
int _day;
};
要点:
每个成员变量在初始化列表中只能出现一次(初始化只能初始化一次)
类中包含以下成员,必须放在初始化列表位置进行初始化:
引用成员变量
const成员变量
自定义类型成员(且该类没有默认构造函数时)
cpp
class A
{
public:
A(int a)
:_a(a)
{}
private:
int _a;
};
class B
{
public:
B(int a, int ref)
:_aobj(a)
,_ref(ref)
,_n(10)
{}
private:
A _aobj; // 没有默认构造函数
int& _ref; // 引用
const int _n; // const
};
3.尽量使用初始化列表初始化,因为不管你是否使用初始化列表,对于自定义类型成员变量,一定会先使用初始化列表初始化。
- 成员变量 在类中声明次序 就是其在初始化列表中的初始化顺序,与其在初始化列表中的先后次序无关
cpp
class A
{
public:
A(int a)
:_a1(a)
,_a2(_a1)
{}
void Print()
{
cout<<_a1<<" "<<_a2<<endl;//打印结果 1 随机值
}
private:
int _a2;
int _a1;
};
int main()
{
A aa(1);
aa.Print();
}
1.3 explicit关键字
构造函数不仅可以构造与初始化对象,对于接收单个参数的构造函数,还具有类型转换的作用。接收单个参数的构造函数具体表现:
构造函数只有一个参数
构造函数有多个参数,除第一个参数没有默认值外,其余参数都有默认值
全缺省构造函数
格式:explicit 函数
cpp
class Date
{
public:
// 1. 单参构造函数,没有使用explicit修饰,具有类型转换作用
explicit Date(int year)
:_year(year)
{}
private:
int _year;
int _month;
int _day;
};
void Test()
{
Date d1(2022);//能过
// 用一个整形变量给日期类型对象赋值
// 实际编译器背后会用2023构造一个无名对象,最后用无名对象给d1对象进行赋值
d1 = 2023;//不能过,触发隐式类型转换
// explicit修饰构造函数,禁止了单参构造函数类型转换的作用,这个会出问题
用
}
用explici修饰构造函数,将会禁止构造函数的隐式转换的功能
二.static成员
声明为static 的类成员 称为类的静态成员 ,用static 修饰的成员变量 ,称之为静态成员变量 ;用static 修饰 的成员函数 ,称之为静态成员函数 。静态成员变量一定要在类外进行初始化
2.1 特性
静态成员 为所有类对象所共享,不属于某个具体的对象,存放在静态区
静态成员变量 必须在类外定义,定义时不添加static关键字,类中只是声明
类静态成员即可用 类名 :: 静态成员 或者 对象 . 静态成员来访问
静态成员函数没有 隐藏的this 指针,不能访问任何非静态成员
静态成员也是类的成员,受public、protected、private 访问限定符的限制
聪明的大家来思考这两个问题为什么可以,为什么不可以!
-
静态成员函数可以调用非静态成员函数吗?不可
-
非静态成员函数可以调用类的静态成员函数吗?可以
三. 友元
3.1 友元函数
友元提供了一种突破封装的方式,虽然有时能带来便利,但也会增加类之间的耦合度,破坏封装性,因此不宜过度使用。
友元主要分为两种:友元函数 和友元类。
友元解决的一个重要问题是什么?
当我们尝试重载
operator<<(输出运算符)时,会发现无法将其重载为成员函数。这是因为 cout 输出流对象与隐含的 this 指针会争夺第一个参数的位置 。成员函数的第一个参数默认为隐藏的 this 指针,这意味着左操作数必须是对象本身。然而在实际使用中,cout 需要作为第一个形参才能正常使用。因此,我们需要将operator<<重载为全局函数,但这又会导致无法访问类的私有成员。此时,友元机制便提供了解决方案。operator>>(输入运算符)的情况与此类似。
cpp
class Date
{
public:
Date(int year, int month, int day)
: _year(year)
, _month(month)
, _day(day)
{}
// d1 << cout; 会转换为 d1.operator<<(&d1, cout),不符合常规调用习惯
// 因为成员函数的第一个参数一定是隐藏的 this,所以 d1 必须放在 << 的左侧
ostream& operator<<(ostream& _cout)
{
_cout << _year << "-" << _month << "-" << _day << endl;
return _cout;
}
private:
int _year;
int _month;
int _day;
};
友元函数 可以直接访问类的私有成员,它是一个定义在类外部的普通函数,不属于任何类。需要在类的内部进行声明,声明时需加上 friend 关键字。
格式:friend 函数
cpp
class Date
{
friend ostream& operator<<(ostream& _cout, const Date& d);
friend istream& operator>>(istream& _cin, Date& d);
public:
Date(int year = 1900, int month = 1, int day = 1)
: _year(year)
, _month(month)
, _day(day)
{}
private:
int _year;
int _month;
int _day;
};
ostream& operator<<(ostream& _cout, const Date& d)
{
_cout << d._year << "-" << d._month << "-" << d._day;
return _cout;
}
istream& operator>>(istream& _cin, Date& d)
{
_cin >> d._year;
_cin >> d._month;
_cin >> d._day;
return _cin;
}
说明:
- 友元函数 可以访问类的私有和保护成员,但不是类的成员函数
- 友元函数不能用 const 修饰
- 友元函数 可以在类定义的任何地方声明,不受类访问限定符限制
- 一个函数可以是多个类的友元函数
- 友元函数的调用方式与普通函数相同
3.2 友元类
友元类的所有成员函数都可以作为另一个类的友元函数,从而访问另一个类中的非公有成员。
友元关系是单向的,不具有交换性。
- 例如,若 Time 类声明 Date 类为其友元类,则 Date 类可以直接访问 Time 类的私有成员变量,但 Time 类无法访问 Date 类的私有成员变量。
友元关系不能传递。
- 如果 B 是 A 的友元,C 是 B 的友元,并不能推出 C 是 A 的友元。
友元关系不能继承,关于这一点我们将在继承部分详细讲解。
四.内部类
概念:
如果一个类定义在另一个类的内部,这个内部类就叫做内部类。内部类是一个独立的类,它不属于外部类,更不能通过外部类的对象去访问内部类的成员。外部类对内部类没有任何越界的访问权限。
注意: 外部类默认是内部类的友元类,内部类可以通过外部类的对象参数来访问外部类中的所有成员。但是内部类不是外部类的友元。
特性:
内部类可以定义在外部类的public、protected、private都是可以的。
注意内部类可以直接访问外部类中的static成员,不需要外部类的对象/类名。
sizeof(外部类)=外部类,和内部类没有任何关系。
cpp
class A
{
private:
static int k;
int h;
public:
class B // A天生就是B的友元
{
public:
void foo(const A& a)
{
cout << k << endl;//OK
cout << a.h << endl;//OK
}
};
};
int A::k = 1;
int main()
{
A::B b;
b.foo(A());
return 0;
}
五.再次理解类与对象
类是对某一类实体(对象)来进行描述的,描述该对象具有那些属性,那些方法,描述完成后就形成了一种新的自定义类型,在用该自定义类型去实例化具体的对象。大家要重点理解类与对象的关系!
🎯 闭幕

🎉 恭喜你完成了 C++ 类与对象核心知识的学习!今天我们系统梳理了 初始化列表 、static 成员 、友元 、内部类 以及 再次理解类与对象 这五大核心模块。
为了巩固知识并进一步拓展,建议你:
🚀 动手实践
在本地 IDE 或在线编译器中编写代码,尝试不同的测试用例,亲身体验每个知识点的用法。
💡 深入思考
初始化列表 :初始化列表和函数体内赋值的 本质区别 是什么?哪些成员 必须 在初始化列表中初始化?初始化列表的初始化顺序 由什么决定,和书写顺序有关吗?
static 成员 :静态成员变量 为什么必须在类外单独定义 ?静态成员函数 为什么不能访问非静态成员 ?静态成员变量 能否在类内初始化(什么情况下可以)?
友元 :友元函数 破坏了封装吗 ?为什么实际开发中要 谨慎使用友元 ?友元关系是 单向还是双向 ?能否传递?
内部类 :内部类和外部类的关系是什么?内部类能否直接访问外部类的非静态成员 ?内部类的 访问权限 受外部类访问限定符的影响吗?
📚 延伸挑战
-
定义一个
Date类,用 初始化列表 初始化const成员和引用成员,演示初始化列表的必要性。 -
定义一个
Student类,使用 静态成员变量 统计学生总人数,使用 静态成员函数 获取总人数。 -
定义一个
Complex复数类,使用 友元函数 重载operator<<和operator>>,实现自定义输入输出。 -
定义一个
Outer类,在内部定义Inner内部类,演示 内部类 访问外部类静态成员的用法。
如果你觉得本文对你有所帮助,欢迎:
👍 点赞 / 收藏
👤 关注作者,获取更多题解
💬 留言交流你的疑问或优化思路
🔍 深入思考参考答案
初始化列表 :初始化列表是 直接构造 成员变量(只调用一次构造函数),赋值是 先构造再赋值 (调用默认构造 + 赋值运算符)。
const成员、引用成员、没有默认构造函数的类成员 必须 使用初始化列表。初始化顺序由 成员声明的顺序 决定,与初始化列表的书写顺序无关。static 成员 :静态成员变量属于类,所有对象共享一份 ,必须在类外单独定义以分配存储空间(C++17 起
inline static可在类内初始化)。静态成员函数 没有this指针,因此不能访问非静态成员。友元 :友元 破坏了封装 ,因为可以访问私有成员。实际开发中应 谨慎使用 ,仅在必要时(如运算符重载)使用。友元关系 单向 (A 是 B 的友元不代表 B 是 A 的友元),不能传递(A 是 B 的友元,B 是 C 的友元,不代表 A 是 C 的友元)。
内部类 :内部类 独立于外部类存在 ,不依赖外部类对象。内部类 不能直接访问外部类的非静态成员 (因为没有外部类对象)。内部类的访问权限受外部类访问限定符影响(如
private Outer::Inner外部无法直接使用)。
🛠️ 延伸挑战参考解法
1. Date 类(初始化列表的必要性)
cpp
/*
* 功能:演示初始化列表的必要性
* 场景:const 成员、引用成员、无默认构造的成员必须用初始化列表
* 重点:初始化顺序由声明顺序决定,与初始化列表书写顺序无关
*/
#include <iostream>
using namespace std;
// ----- 辅助类:没有默认构造函数 -----
class NoDefault
{
private:
int _val;
public:
NoDefault(int val) // 只有带参构造,没有默认构造
: _val(val)
{
cout << "NoDefault(int) constructed: " << _val << endl;
}
void Print() const
{
cout << "NoDefault: " << _val << endl;
}
};
// ----- Date 类演示初始化列表的必要性 -----
class Date
{
private:
// const 成员:必须在初始化列表中初始化,不能赋值
const int _year;
// 引用成员:必须在初始化列表中初始化,不能赋值
int& _ref;
// 没有默认构造函数的类成员:必须显式初始化
NoDefault _nd;
// 普通成员:可以在初始化列表或函数体内赋值
int _month;
int _day;
public:
// 推荐写法:全部使用初始化列表
Date(int year, int& ref, int ndVal, int month, int day)
: _year(year) // const 成员必须初始化
, _ref(ref) // 引用成员必须初始化
, _nd(ndVal) // 无默认构造的成员必须初始化
, _month(month) // 普通成员推荐用初始化列表(效率更高)
, _day(day)
{
cout << "Date constructed with initialization list" << endl;
// _year = 2024; // 错误!const 成员不能赋值
// _ref = ref; // 错误!引用成员不能赋值(只能初始化)
}
// ----- 错误写法(会编译失败)-----
// Date(int year, int& ref)
// {
// _year = year; // 错误!const 成员不能赋值
// _ref = ref; // 错误!引用成员不能赋值
// // _nd 没有默认构造,编译错误
// }
void Print() const
{
cout << "Year: " << _year << endl;
cout << "Ref: " << _ref << endl;
_nd.Print();
cout << "Month: " << _month << ", Day: " << _day << endl;
}
};
int main()
{
int x = 100;
Date d(2024, x, 50, 8, 5);
d.Print();
return 0;
}
2. Student 类(静态成员变量统计总人数)
cpp
/*
* 功能:演示静态成员变量和静态成员函数的使用
* 场景:统计学生总人数,所有对象共享一份计数
* 重点:静态成员变量类外初始化,静态成员函数无 this 指针
*/
#include <iostream>
#include <string>
using namespace std;
class Student
{
private:
string _name;
int _age;
int _id;
// 静态成员变量:属于类,所有对象共享
static int _totalCount; // 统计学生总人数
public:
// 构造函数:每创建一个学生,总人数加 1
Student(const string& name, int age, int id)
: _name(name), _age(age), _id(id)
{
_totalCount++;
cout << "Student " << _name << " created. Total: " << _totalCount << endl;
}
// 析构函数:每销毁一个学生,总人数减 1
~Student()
{
_totalCount--;
cout << "Student " << _name << " destroyed. Total: " << _totalCount << endl;
}
// 静态成员函数:获取总人数
// 特点:没有 this 指针,只能访问静态成员
static int GetTotalCount()
{
// 不能访问非静态成员
// cout << _name; // 编译错误!
return _totalCount;
}
void PrintInfo() const
{
cout << "Name: " << _name << ", Age: " << _age << ", ID: " << _id << endl;
}
};
// 静态成员变量必须在类外初始化(分配存储空间)
int Student::_totalCount = 0;
int main()
{
cout << "Initial total: " << Student::GetTotalCount() << endl; // 0
Student s1("Alice", 20, 1001);
Student s2("Bob", 21, 1002);
Student s3("Charlie", 22, 1003);
cout << "Final total: " << Student::GetTotalCount() << endl; // 3
return 0;
}
3. Complex 复数类(友元函数重载 << 和 >>)
cpp
/*
* 功能:演示友元函数的使用
* 场景:重载 operator<< 和 operator>>,实现自定义输入输出
* 重点:友元函数可以访问类的私有成员
*/
#include <iostream>
using namespace std;
class Complex
{
private:
double _real; // 实部
double _imag; // 虚部
public:
// 构造函数(带缺省参数)
Complex(double real = 0.0, double imag = 0.0)
: _real(real), _imag(imag)
{
cout << "Complex(" << _real << ", " << _imag << ") constructed" << endl;
}
// 打印函数
void Print() const
{
if (_imag >= 0)
cout << _real << " + " << _imag << "i" << endl;
else
cout << _real << " - " << -_imag << "i" << endl;
}
// 声明友元函数:重载运算符 <<(输出)
// 注意:友元不是成员函数,它是全局函数
friend ostream& operator<<(ostream& os, const Complex& c);
// 声明友元函数:重载运算符 >>(输入)
friend istream& operator>>(istream& is, Complex& c);
};
// ----- 友元函数定义(可以访问 Complex 的私有成员) -----
ostream& operator<<(ostream& os, const Complex& c)
{
// 直接访问私有成员 _real 和 _imag
if (c._imag >= 0)
os << c._real << " + " << c._imag << "i";
else
os << c._real << " - " << -c._imag << "i";
return os; // 返回 ostream 引用,支持链式输出
}
istream& operator>>(istream& is, Complex& c)
{
// 直接访问私有成员 _real 和 _imag
cout << "Enter real and imag: ";
is >> c._real >> c._imag;
return is; // 返回 istream 引用,支持链式输入
}
int main()
{
Complex c1(3.0, 4.0);
cout << "c1 = " << c1 << endl; // 调用友元 operator<<
Complex c2;
cin >> c2; // 调用友元 operator>>
cout << "c2 = " << c2 << endl;
return 0;
}
4. 内部类(Outer 和 Inner)
cpp
/*
* 功能:演示内部类的用法
* 场景:Outer 类内部定义 Inner 内部类,Inner 访问 Outer 的静态成员
* 重点:内部类不依赖外部类对象,不能直接访问外部类非静态成员
*/
#include <iostream>
#include <string>
using namespace std;
class Outer
{
private:
string _name;
static int _staticData; // 外部类的静态成员
public:
Outer(const string& name)
: _name(name)
{}
// ----- 内部类定义 -----
class Inner
{
private:
int _id;
public:
Inner(int id = 0)
: _id(id)
{
cout << "Inner constructed, id=" << _id << endl;
}
// 内部类可以访问外部类的静态成员(因为静态成员属于类,不需要对象)
void ShowOuterStatic() const
{
cout << "Outer::_staticData = " << Outer::_staticData << endl;
}
// 不能直接访问外部类的非静态成员(没有外部类对象)
// void ShowOuterName() const
// {
// cout << Outer::_name << endl; // 编译错误!
// }
// 想要访问外部类非静态成员,需要传入外部类对象
void ShowOuterName(const Outer& o) const
{
cout << "Outer::_name = " << o._name << endl; // 通过对象访问
}
void Print() const
{
cout << "Inner id: " << _id << endl;
}
};
void Print() const
{
cout << "Outer name: " << _name << endl;
}
// 访问限定符不影响内部类本身,但影响外部使用
// 如果 Inner 是 private,外部就无法使用 Outer::Inner 类型
};
// 外部类静态成员初始化
int Outer::_staticData = 42;
int main()
{
// 创建外部类对象
Outer outer("Hello");
// 创建内部类对象(独立于外部类对象,不需要外部类实例)
Outer::Inner inner(100); // Inner 是 public 时才能这样使用
inner.ShowOuterStatic(); // 访问外部类静态成员
inner.ShowOuterName(outer); // 需要传入外部类对象才能访问非静态成员
inner.Print();
return 0;
}
祝你在 C++ 的学习之路上越走越稳,早日掌握更多编程技巧!下次见 🚀✨