【初始化列表 + 自定义类型转换 + static成员】目录
- 前言:
- ---------------初始化列表---------------
- 什么是初始化列表?
- 为什么要使用初始化列表?
- 怎么使用初始化列表?
- 初始化列表 vs 构造函数体内赋值谁能获胜?
- 使用初始化列表的注意事项有哪些?
- 勇敢的少年,快去创造奇迹吧?
- C++11中的新特性缺省成员初始化怎么使用?
- 成员变量走初始化列表的时机是什么?
- ---------------用户自定义类型转换---------------
- 用户自定义类型转换有哪些?
- 什么是explicit关键字?
- 为什么需要explicit关键字?
- 怎么使用explicit关键字?
- ---------------static成员---------------
- 什么是static成员?
- 什么是static成员变量?
- static成员变量的特点有哪些?(大总结)
- 什么是static成员函数?
- static成员函数的特点有哪些?(大总结)
- 静态成员怎么使用?
往期《C++初阶》回顾:
/------------ 入门基础 ------------/
【C++的前世今生】
【命名空间 + 输入&输出 + 缺省参数 + 函数重载】
【普通引用 + 常量引用 + 内联函数 + nullptr】
/------------ 类和对象 ------------/
【类 + 类域 + 访问限定符 + 对象的大小 + this指针】
【类的六大默认成员函数】
前言:
哈喽呀小伙伴们~一周不见想死你们啦!(づ。◕‿‿◕。)づ
上周有没有乖乖学习呀?🤔
至于博主我嘛👉 刚忙完期末复习大作战💻 ,博主可是被期末复习按在地上摩擦了一周呢 (╯°□°)╯︵ ┻━┻
不过现在终于忙完啦!虽然还有一周才考试…但是!咱这闲不住的人哪能真闲着呀✨
所以!今天就马不停蹄给大家带来《C++ 初阶之类和对象》【下】的第一篇宝藏博客啦🎁(๑•̀ㅂ•́)و✧
据说看完这篇的小伙伴都直呼"原来C++还能这样玩?"(≧∇≦)ノ 快搬好小板凳准备上课啦~
---------------初始化列表---------------
什么是初始化列表?
初始化列表(Initializer List)
:是 C++ 中用于在对象构造时直接初始化成员变量的语法。
它位于构造函数的参数列表之后,函数体之前,以冒号
:
开始,后面跟着用逗号分隔的成员变量初始化列表。它核心作用是在对象内存分配完成后立即初始化成员,而非先默认初始化再赋值。
class MyClass {int _a;double _b;std::string _s; public:// 初始化列表语法MyClass(int a, double b, const std::string& s) : _a(a), _b(b), _s(s) // 初始化列表{// 构造函数体(此时成员已初始化)} };
为什么要使用初始化列表?
使用初始化列表主要有以下两点原因:
- 为了提高类的成员变量的初始化效率
- 有些成员变量不得不使用初始化列表进行初始化
--------------------
为了提高效率
--------------------对于类类型的成员变量 :
- 如果没有使用初始化列表,在进入构造函数体之前,会先调用成员变量的默认构造函数
进行初始化
,然后在构造函数体中再进行赋值
操作,这会产生额外的开销。- 而使用初始化列表,可以直接调用成员变量的合适构造函数
进行初始化
,避免了先默认构造再赋值的过程,从而提高了初始化效率。
- 例如:当成员变量是
string
类型时,使用初始化列表可以直接调用string
的构造函数来初始化,而不是先默认构造一个空字符串,再进行赋值。class Student {string _name; public:// 低效写法:先默认构造空字符串,再赋值Student(const std::string& name) { _name = name; }// 高效写法:直接调用string的拷贝构造Student(const string& name) : _name(name) {} };
--------------------
不得不使用
--------------------
初始化 const 成员和引用成员
:
const
成员变量和引用成员在定义后就不能被修改,因此必须在定义时进行初始化。初始化列表是在构造函数中初始化
const
成员和引用成员的唯一方法。如果不使用初始化列表,试图在构造函数体中对const
成员或引用成员进行赋值,将会导致编译错误。----------------------------初始化 const 成员---------------------------- class ConstDemo {const int _value; public:ConstDemo(int v) : _value(v) {} // 必须用初始化列表 };----------------------------初始化 const 成员----------------------------class RefDemo {int& _ref; public:RefDemo(int& r) : _ref(r) {} // 必须用初始化列表 };
调用无默认构造的成员
:
当成员变量的类没有默认构造函数,只有带参数的构造函数时,必须使用初始化列表来传递参数进行初始化。
否则,编译器无法找到合适的构造函数来初始化成员变量,从而引发编译错误。
class Engine { public:Engine(int power) {} // 只有带参构造 }; class Car {Engine _engine; public:Car() : _engine(100) {} // 必须显式初始化 };
怎么使用初始化列表?
#include<iostream>
using namespace std;// Time类定义
class Time
{
public:/*---------------Time类的构造函数(带参数)---------------*/// 注意:这个类没有提供默认构造函数(无参构造函数)Time(int hour):_hour(hour) {cout << "Time类的构造函数Time()" << endl; }
private:int _hour;
};// Date类定义
class Date
{
public:/*---------------Date类的构造函数---------------*/Date(int& x, int year = 1, int month = 1, int day = 1):_year(year) // 初始化_year, _month(month) // 初始化_month, _day(day) // 初始化_day, _t(12) // 初始化Time类成员(必须提供参数), _ref(x) // 初始化引用成员(必须通过初始化列表), _n(1) // 初始化const成员(必须通过初始化列表){// 如果尝试在构造函数体内初始化以下成员,会导致编译错误:// 1. _t(12) - 错误:Time类没有默认构造函数// 2. _ref = x - 错误:引用必须在初始化列表中初始化// 3. _n = 1 - 错误:const成员必须在初始化列表中初始化}/*---------------打印日期信息---------------*/void Print() const{cout << _year << "-" << _month << "-" << _day << endl; //const成员函数,保证不会修改对象状态}private:int _year; int _month; int _day; Time _t; // Time类成员(没有默认构造函数)int& _ref; // 引用成员 (必须在初始化列表中初始化)const int _n; // const成员 (必须在初始化列表中初始化)
};int main()
{int i = 0; //定义一个整型变量用于初始化Date的引用成员// 创建Date对象d1// 参数:// i - 用于初始化_ref引用成员// 其他参数使用默认值Date d1(i);// 调用Print方法输出日期信息d1.Print();return 0;
}/** 关键点总结:* 1. 初始化列表的必要性:* - 必须用于初始化:引用成员(_ref)、const成员(_n)、没有默认构造函数的类成员(_t)** 2. 初始化顺序:* - 成员变量的初始化顺序由它们在类中的声明顺序决定(_year→_month→_day→_t→_ref→_n)* - 与初始化列表中的书写顺序无关** 3. 特殊成员初始化:* - 引用和const成员:只能在初始化列表中初始化* - 没有默认构造的类成员:必须在初始化列表中显式构造** 4. 良好实践:* - 即使对普通成员变量(如:_year)也使用初始化列表* - 保持初始化列表顺序与成员声明顺序一致*/
初始化列表 vs 构造函数体内赋值谁能获胜?
特性 | 初始化列表 | 构造函数内赋值 |
---|---|---|
const成员 | ✔️ 支持 | ❌ 编译错误 |
引用成员 | ✔️ 支持 | ❌ 编译错误 |
无默认构造的成员 | ✔️ 支持 | ❌ 编译错误 |
性能 | 更高效 (直接构造) | 可能低效 (先默认构造再赋值) |
初始化顺序 | 由成员声明顺序决定 | 无顺序依赖 |
使用初始化列表的注意事项有哪些?
在 C++ 中,成员变量的初始化遵循两大规则:
其一:初始化顺序仅取决于变量在类中声明的先后次序,与初始化列表中的排列顺序无关。
class Order {int a; // 先声明int b; public:Order() : b(1), a(2) {} // 实际初始化顺序:a→b };
其二:每个成员变量在初始化列表中只能出现一次,作为其完成定义与初始化的关键位置,确保对象创建时各成员获得唯一且正确的初始状态。
勇敢的少年,快去创造奇迹吧?
下面是一道关于:初始化顺序的自测题(看完代码不要看下面的注释分析,因为那是答案)😄
#include <iostream>
using namespace std;class A
{
public:/*---------------构造函数---------------*//*** 初始化列表说明:* 1. _a1初始化为参数a的值* 2. _a2初始化为_a1的值(此时_a1尚未初始化,存在风险)** 注意:成员初始化顺序由声明顺序决定,与初始化列表顺序无关*/A(int a):_a1(a) // 初始化_a1为传入的参数a, _a2(_a1) // 用_a1的值初始化_a2(此时_a1尚未初始化){}/*---------------打印成员变量值---------------*/void Print() {cout << _a1 << " " << _a2 << endl;}private:// 注意成员变量的声明顺序:_a2在前,_a1在后// 这将影响初始化顺序,即使初始化列表中的顺序不同int _a2 = 2; // 成员_a2,有类内初始值2(但会被初始化列表覆盖)int _a1 = 2; // 成员_a1,有类内初始值2(但会被初始化列表覆盖)
};int main()
{// 创建A类对象,传入参数1A aa(1);// 打印对象成员值aa.Print();return 0;
}-------------------------------我下面是:分析和答案----------------------------/** 关键点分析:* 1. 成员初始化顺序:* - 严格按照类中声明的顺序进行(先_a2,后_a1)* - 与初始化列表中的书写顺序无关** 2. 初始化过程:* a) 首先尝试初始化_a2:* - 使用_a1的值(此时_a1尚未初始化,值不确定)* - 类内初始值2被初始化列表覆盖* b) 然后初始化_a1:* - 使用构造函数参数1* c) 最后执行构造函数体(本例为空)** 4. 类内初始值的作用:* - 如果初始化列表不显式初始化成员,则使用类内初始值* - 本例中初始化列表覆盖了类内初始值*/
C++11中的新特性缺省成员初始化怎么使用?
类成员变量可以在声明时直接指定初始值(缺省值),缺省值会在构造函数调用前生效,这些值会在对象构造时被使用,除非初始化列表中显式覆盖它们。
#include<iostream>
using namespace std;// Time类定义
class Time
{
public:/*---------------Time类构造函数---------------*/Time(int hour):_hour(hour) {cout << "Time类构造函数Time(int hour)调用" << endl; }
private:int _hour;
};// Date类定义
class Date
{
public:/*---------------Date类默认构造函数---------------*/Date():_month(2) // 显式初始化_month为2(会覆盖缺省值1){// 构造函数体内可以执行其他操作cout << "Date类默认构造函数Date()调用" << endl; /** 注意:以下成员已在类定义中提供了缺省值:* _year = 1* _day 未显式初始化(内置类型未初始化时为随机值)* _t = 1 (调用Time(int)构造函数)* _n = 1 (const成员)* _ptr = (int*)malloc(12) (动态分配内存)*/}/*---------------打印日期信息---------------*/void Print() const{// 注意:_day未初始化,打印的值是未定义的cout << _year << "-" << _month << "-" << _day << endl;}private:// 成员变量声明与缺省值初始化(C++11特性)// 内置类型缺省值(如果初始化列表不显式初始化则使用此值)int _year = 1; // 年份,缺省值为1int _month = 1; // 月份,缺省值为1(但构造函数初始化列表会覆盖为2)int _day; // 日期,没有缺省值(未初始化)// 类类型成员缺省初始化Time _t = 1; // 调用Time(int)构造函数初始化// const成员缺省值const int _n = 1; // const成员必须在声明时或初始化列表中初始化// 指针成员动态内存分配int* _ptr = (int*)malloc(12); // 使用malloc分配12字节内存
};int main()
{// 创建Date对象d1Date d1; //调用默认构造函数// 打印日期信息d1.Print(); //输出格式:year-month-dayreturn 0;
}/** 关键点总结:* 1. 缺省成员初始化(C++11特性):* - 在类声明中直接为成员变量指定初始值* - 如果构造函数初始化列表没有显式初始化,则使用缺省值(初始化列表的优先级高于缺省值)* - 初始化列表的初始化会覆盖缺省值(如:_month被初始化为2而非1)** 2. 成员初始化顺序:* - 首先按照缺省值初始化* - 然后执行构造函数初始化列表的初始化* - 最后执行构造函数体内的代码** 3. 特殊成员处理:* - _day:没有缺省值,未在初始化列表中初始化,值是未定义的* - _t:类类型成员,使用Time(int)构造函数初始化* - _n:const成员,必须在声明时或初始化列表中初始化* - _ptr:使用malloc动态分配内存*/
成员变量走初始化列表的时机是什么?
初始化列表的特点:
先于构造函数体
:无论成员变量是否在初始化列表中显式列出,其初始化都在构造函数体执行之前
完成隐式初始化
:未在初始化列表中显式初始化的成员变量,仍会按默认规则初始化(如:默认构造函数、缺省值或未定义值)在 C++ 中,推荐优先使用初始化列表对成员变量进行初始化,
原因如下:即使成员变量未在初始化列表中显式初始化,其初始化过程依然会经过初始化列表阶段。
若成员变量在声明时指定了默认值,初始化列表将直接采用该默认值进行初始化。
若未指定默认值:
对于内置类型成员
,其是否初始化由编译器决定,C++ 标准未作强制规定,这可能导致变量处于未初始化的不确定状态。对于自定义类型成员
:
- 不存在默认构造函数:若该自定义类型不存在默认构造函数,编译器无法自动完成初始化,就会引发编译错误。
- 存在默认构造函数:
- 若自定义类型成员未在初始化列表中显式初始化,编译器将调用该类型的
默认构造函数
进行初始化。- 若自定义类型成员在初始化列表中显式初始化,编译器将直接调用该类型对应的
构造函数
,而非默认构造函数。
#include <iostream>
using namespace std;class MyClass
{
public:MyClass(){cout << "默认构造函数被调用" << endl;}MyClass(int value){cout << "带参构造函数被调用,参数:" << value << endl;}
};// 案例1:未显式初始化(调用默认构造函数)
class Container1
{
private:MyClass obj1;
public:Container1() //未在初始化列表中显式初始化{cout << "Container1构造函数体执行" << endl;}
};// 案例2:显式初始化(调用带参构造函数)
class Container2
{
private:MyClass obj2;
public:Container2() : obj2(42) //显式初始化{cout << "Container2构造函数体执行" << endl;}
};int main()
{cout << "==== 案例1 ====" << endl;Container1 c1;cout << "\n==== 案例2 ====" << endl;Container2 c2;return 0;
}
---------------用户自定义类型转换---------------
用户自定义类型转换有哪些?
自定义类型转换的种类:
内置类型 → 类类型
:
当类定义了单参数构造函数(或:除第一个参数外其余参数均有默认值)时,C++ 允许将该参数类型的值隐式转换为类对象。
示例:class MyClass { public:MyClass(int value) { /* ... */ } // 单参数构造函数 };void func(MyClass obj);func(42); // 隐式转换:int → MyClass
类类型 → 类类型
:
若类 A 定义了以类 B 为参数的构造函数,则类 B 的对象可隐式转换为类 A 的对象。
示例:class B {}; class A { public:A(const B& b) { /* ... */ } // 以B为参数的构造函数 };void func(A obj); B b; func(b); // 隐式转换:B → A
注意:上面的这两种类型转换都是隐式类型转换
代码小案例:展示使用自定义类型的类型转换
#include<iostream>
using namespace std;class A
{
public:/*----------------------单参数构造函数----------------------*//*** 注意:如果加上explicit关键字,将禁止隐式类型转换* 例如:A aa1 = 1; 这样的语句将无法编译*/A(int a1):_a1(a1){}/*----------------------双参数构造函数----------------------*//*** 注意:如果加上explicit关键字,将禁止隐式类型转换* 例如:A aa3 = {2,2}; 这样的语句将无法编译*/A(int a1, int a2):_a1(a1), _a2(a2){}/*----------------------打印成员变量值----------------------*/void Print(){cout << _a1 << " " << _a2 << endl;}int Get() const{return _a1 + _a2;}private:int _a1 = 1; // 成员变量_a1,默认值为1int _a2 = 2; // 成员变量_a2,默认值为2
};class B
{
public:/*----------------------构造函数,接收A类型的引用----------------------*//*** 这个构造函数允许从A类型到B类型的隐式转换*/B(const A& a):_b(a.Get()) // 使用A对象的Get()方法初始化_b{}private:int _b = 0; // 成员变量_b,默认值为0
};int main()
{// 1. 隐式类型转换示例1:int → A// 编译器实际执行:// 1) 先用1构造一个临时A对象// 2) 再用这个临时对象拷贝构造aa1// 3) 编译器优化为直接构造A aa1 = 1; // 等价于 A aa1(1);aa1.Print(); // 输出:1 2// 2. 隐式类型转换示例2:int → A → const A&// 用1构造临时A对象,然后绑定到引用aa2const A& aa2 = 1; // 临时对象生命周期延长至引用作用域结束// 3. C++11列表初始化(多参数隐式转换)// 使用初始化列表构造A对象A aa3 = { 2,2 }; // 等价于 A aa3(2,2);aa3.Print(); // 输出:2 2// 4. 隐式类型转换示例3:A → B// 通过A对象隐式构造B对象// 实际过程:// 1) 调用A::Get()获取值// 2) 用该值构造B对象B b = aa3; // 等价于 B b(aa3);// 5. 隐式类型转换示例4:A → B → const B&// 通过A对象隐式构造临时B对象,然后绑定到引用const B& rb = aa3;return 0;
}/** 关键点总结:* 1. 隐式类型转换规则:* - 当类有单参数构造函数时,支持从参数类型到类类型的隐式转换* - C++11开始支持多参数构造函数的隐式转换(使用初始化列表语法)** 2. explicit关键字的作用:* - 修饰构造函数时,禁止隐式类型转换* - 需要显式调用构造函数(如 A aa1(1) 而不是 A aa1 = 1)** 3. 引用绑定临时对象:* - const引用可以绑定到隐式转换产生的临时对象* - 临时对象的生命周期会延长至引用的作用域结束*/
什么是explicit关键字?
explicit 关键字
:是 C++ 中用于修饰 构造函数 或 类型转换运算符 的关键字。
- 它的核心作用是 禁止隐式类型转换,强制要求程序员进行显式转换,从而提高代码的安全性和可读性。
示例:
class MyClass { public:explicit MyClass(int value) { /* ... */ } // 禁止隐式转换 };void func(MyClass obj);func(42); // 错误:无法隐式转换 func(MyClass(42)); // 正确:显式构造
为什么需要explicit关键字?
在没有
explicit
的情况下,C++ 允许 隐式转换,这可能导致意外的行为。class MyString { public:MyString(const char* str) { /* 构造函数 */ }void print() { cout << str; } private:string str; };void func(MyString s) { s.print(); }int main() {func("Hello"); // 隐式转换:const char* → MyStringreturn 0; }
问题:
func("Hello")
会自动调用MyString(const char*)
构造函数,但可能并非程序员本意。- 如果
MyString
有多个构造函数,隐式转换可能导致歧义。
怎么使用explicit关键字?
修饰构造函数
:禁止编译器进行隐式构造,必须显式调用。class MyString { public:explicit MyString(const char* str) { /* ... */ } // 禁止隐式转换 };void func(MyString s) { s.print(); }int main() {// func("Hello"); // ❌ 错误:不能隐式转换func(MyString("Hello")); // ✅ 必须显式构造return 0; }
修饰类型转换运算符
:禁止隐式类型转换,必须显式转换。class MyInt { public:explicit operator int() const //禁止隐式转换{ return value;} private:int value; };int main() {MyInt num;// int x = num; // ❌ 错误:不能隐式转换int x = static_cast<int>(num); // ✅ 必须显式转换return 0; }
---------------static成员---------------
什么是static成员?
static成员
:是类的特殊成员,它们 不属于任何一个类对象,而是 属于类本身,被所有类对象共享。
static
成员分为两种:
- 静态成员变量(Static Data Members)
- 静态成员函数(Static Member Functions)
什么是static成员变量?
static成员变量
:用static
修饰的成员变量。
定义与特性:
- 它属于整个类,而非类的某个具体对象。
- 无论类创建了多少个对象,
static
成员变量只有一份实例,被所有对象共享。内存分配与初始化:
它在程序启动时分配内存。
它必须在类外进行初始化。(即使有默认值也需在类外显式初始化)
class MyClass { public:static int sharedVar; //声明静态成员变量 }; int MyClass::sharedVar = 10; //在类外初始化
访问方式:
- 可以通过类名加作用域解析符
::
直接访问。
- 例如:
MyClass obj; int value1 = MyClass::sharedVar;
- 也可以能通过对象来访问。
- 例如:
int value2 = obj.sharedVar;
,但推荐用类名访问,更能体现其静态属性。作用:常用于统计类对象的数量,或者存储类的全局相关信息。
static成员变量的特点有哪些?(大总结)
1. 存储特性
全局唯一性
:无论创建多少个类的对象,static
成员变量在内存中仅有一份实例,被所有对象共享。静态存储区
:存储在程序的静态存储区,生命周期从程序启动到结束,不依赖于对象的创建与销毁。
2. 初始化规则
类外显式初始化
:必须在类外进行初始化。不支持类内默认值
:C++ 标准禁止在类内直接为static
成员变量设置缺省值。(除非是constexpr
类型)
3. 访问特性
类名直接访问
:可通过类名::静态成员名
访问,无需创建对象。受访问限定符约束
:与普通成员变量相同,受public
/protected
/private
限制。
4. 功能特性
类级别数据
:用于存储与类相关的全局信息(如:对象计数、配置参数)独立于对象
:不与任何对象绑定,可在无对象实例时使用。可被const成员函数修改
:static
成员变量可在const
成员函数中被修改,因其不属于任何对象实例。
什么是static成员函数?
static 成员函数
:用static
修饰的成员函数。
定义与特性:
同样属于类本身,它不依赖于类的具体对象,没有隐含的
this
指针(不能访问非静态成员变量
和非静态成员函数
)class MyClass { public:static void staticFunc() {// 这里只能访问静态成员变量,不能访问非静态成员// 如不能访问非静态成员变量:nonStaticVar}static int sharedVar; private:int nonStaticVar; }; int MyClass::sharedVar = 0;
调用方式:
- 可以通过类名直接调用。
- 例如:
MyClass::staticFunc();
- 也可以通过对象调用,但不推荐。
用途:通常用于实现一些与类整体相关,而不涉及具体对象状态的功能。
- 例如:为类提供工具性的方法,或操作类的静态成员变量。
static成员函数的特点有哪些?(大总结)
1. 归属特性
属于类而非对象
:static
成员函数不绑定到任何对象实例,即使没有创建类的对象也可调用。无this指针
:无法使用this
指针,因此不能访问
非静态成员变量或调用
非静态成员函数。
2. 调用方式
类名直接调用
:通过类名::静态函数名()
调用,无需创建对象。对象调用允许但不推荐
:也可通过对象实例调用(如:obj.staticFunc()
),但语义上不直观。
3. 访问权限
仅访问静态成员
:只能访问类的静态成员变量和静态成员函数,无法访问非静态成员(包括普通成员变量和非静态成员函数)。受访问限定符约束
:与普通成员函数相同,受public
/protected
/private
限制。
4. 功能特性
工具性方法
:常用于实现与类相关但不依赖对象状态的工具函数(如:工厂方法、配置管理)全局接口封装
:可作为类的全局接口,隐藏类的实现细节(如:单例模式中的getInstance()
方法)
5. 特殊规则
不能声明为const
:由于没有this
指针,static
成员函数不能被声明为const
不能是虚函数
:static
成员函数不参与多态,无法被声明为virtual
可在类内定义
:可直接在类内实现,也可在类外实现(需指定类名::
作用域)
静态成员怎么使用?
代码案例:使用静态成员变量和静态成员函数
#include<iostream>
using namespace std;class A
{
public:/*----------------------默认构造函数----------------------*/A(){++_scount; // 每创建一个对象时,静态计数器_scount加1}/*----------------------拷贝构造函数----------------------*/A(const A& t){++_scount; // 每拷贝构造一个对象时,静态计数器_scount加1}/*----------------------析构函数----------------------*/~A(){--_scount; // 对象销毁时,静态计数器_scount减1}/*----------------------静态成员函数----------------------*//*** @brief 静态成员函数:获取当前对象数量* @return 当前存在的对象数量** 注意:* 1. 静态成员函数可以直接通过类名调用* 2. 静态成员函数只能访问静态成员变量*/static int GetACount(){return _scount;}private:// 静态成员变量声明(类内)static int _scount; //用于记录当前存在的A类对象数量
};// 静态成员变量定义和初始化(类外)
// 必须单独在类外进行定义和初始化
int A::_scount = 0; int main()
{// 测试1:初始对象数量cout << A::GetACount() << endl; // 输出:0(尚未创建任何对象)// 测试2:创建对象后的数量A a1, a2; // 调用两次默认构造函数(_scount = 2)A a3(a1); // 调用拷贝构造函数(_scount = 3)cout << A::GetACount() << endl; // 输出:3(三个对象存在)// 测试3:通过对象调用静态成员函数// 语法允许但不推荐,容易造成误解cout << a1.GetACount() << endl; // 输出:3(与类名调用结果相同)// 测试4:尝试直接访问私有静态成员(编译错误)// 错误原因:_scount是private成员,只能在类内部访问//cout << A::_scount << endl; return 0;// main函数结束时,a1,a2,a3依次析构,_scount变为0
}/** 关键点总结:* 1. 静态成员变量_scount:* - 在类内声明,类外初始化* - 所有对象共享同一个_scount* - 用于记录程序中A类对象的实时数量** 2. 静态成员函数GetACount():* - 可以直接通过类名调用(推荐方式)* - 也可以通过对象调用(语法允许但不推荐)* - 只能访问静态成员变量(无法访问普通成员变量)** 3. 访问控制:* - _scount是private成员,外部无法直接访问* - 必须通过public的GetACount()方法获取*/