☰
【C++修炼】类和对象(下):再探构造、类型转换、友元函数、static成员、内部类、匿名对象
2026/10/16 2:18:16 网站建设 项目流程

目录

类和对象

1. 再探构造函数

2. 类型转换

3. static成员

4. 友元函数

5. 内部类

6. 匿名对象


类和对象

1. 再探构造函数

之前我们实现构造函数时,初始化成员变量主要使⽤函数体内赋值,例如带参构造,在函数体内用形参为成员变量赋值。但其实还有一种初始化成员变量的⽅式,且更推荐这种方式——初始化列表。

  • 使用初始化列表的⽅式是:在函数体{}前,形参列表后,以冒号:开始,以逗号,分隔,显式写出需要初始化的成员变量,后跟括号,括号内是需要初始化的值或表达式。
  • 每个成员变量在初始化列表中只能出现⼀次,语法理解上,初始化列表被认为是每个成员变量定义与初始化的地⽅,同一作用域下变量自然只能定义一次,而类体内是成员变量声明的地方。

代码示例1.1

#include<iostream> using namespace std; class Date { public: // 构造函数,初始化成员变量的方式,第二种——显式使用初始化列表 // 书写格式如下,成员变量可分多行写,也可一行全部写完 Date(int year, int month, int day) :_year(year) // 成员变量在构造函数的初始化列表处定义,并开空间 ,_month(month) // 成员变量也可以在此处初始化 ,_day(day) ,_num( (10 * 2) + 1 ) // 括号内可以是表达式,用表达式的返回值进行初始化 ,_ptr( malloc( sizeof(int) * 4 ) // 函数表达式,malloc的返回值作初始值 { // 走完初始化列表,也可以在函数体内执行其他语句 // 例如判断malloc是否成功开辟空间,失败则_ptr被初始化为空指针 if (nullptr == _ptr) { perror("malloc fail"); return; } } void Print() { cout << _year << "年" << _month << "月" << _day << "日" << endl; cout << "_num:>" << _num << endl; } private: // 成员变量在类体内声明:未实例化,不开空间 int _year; int _month; int _day; int _num; int* _ptr; }; int main() { // 对象的定义,调用带参构造,走带参构造的初始化列表进行成员变量的定义与初始化 Date d(2022, 2, 2); d.Print(); return 0; }

运行结果:

这里额外说明一下,以下三个概念需要特别区分:声明、定义与初始化。它们经常容易混淆,但其实是三种不同的行为,只是通常关系密切。类中只声明了成员变量,如果不实例化,创建对象,就不会自动调用构造函数,更不会走构造函数的初始化列表,对成员变量进行定义与初始化。不定义,那么变量就不存在,没有物理内存空间,就不能存储数据,更别谈初始化成员变量。

我们只需要牢记:在语法逻辑上,成员变量是在类中声明,是在初始化列表中定义与初始化,只是部分变量也可以不初始化,或没有初始化的需求,因此不是非要走初始化列表初始化,可以后续在函数体内赋值。

  • 尽量使⽤初始化列表初始化,因为⽆论是否显式写初始化列表,每个构造函数都有初始化列表;⽆论是否在初始化列表显式初始化成员变量,每个成员变量都要⾛初始化列表初始化。
  • 引⽤成员变量、const成员变量、没有默认构造的类类型成员变量,都是定义就必须初始化的变量,因此只能在初始化列表初始化,不能在函数体内初始化,会编译报错。

额外注明:const将变量从普通变量变为常量,一经定义不可修改,因此必须在定义时就初始化,引用同理;类类型成员变量,在定义时一定自动调用构造函数进行初始化,如果没有默认构造,就必须手动传参否则无法调用,手动传参就需要在初始化列表显式去写。

代码示例 1.2

#include<iostream> using namespace std; class Time { public: // 类Time没有默认构造:已有构造,则编译器不能生成构造,且没有无参构造、全缺省构造 // 未显式写初始化列表,但也有隐式的初始化列表 Time(int hour, int minute, int second) { // 即使在函数体内赋值初始化,也要先走初始化列表,再执行函数内的语句 _hour = hour; _minute = minute; _second = second; } void Print() { cout << _hour << "h" << _minute << "m" << _second << "s" << endl; } private: int _hour; int _minute; int _second; }; class Date { public: // 引⽤成员变量,const成员变量,没有默认构造的类类型成员变量必须在初始化列表初始化 Date(int year, int month, int day) :_year(year) ,_month(month) ,_day(day) ,_ref(_year) ,_n(100) ,_t(0, 0, 0) // Time类对象_t的定义,Time类只有带参构造,必须传参才能调用 { // ... } void Print() { cout << _year << "年" << _month << "月" << _day << "日" << endl; cout << "_ref:" << _ref << endl; cout << "const:" << _n << endl; cout << "_t:"; _t.Print(); } private: int _year; int _month; int _day; // 必须在初始化列表初始化 int& _ref; // 引用类型成员变量 const int _n; // const成员变量 Time _t; // 类类型成员变量 }; int main() { Date d(2022, 2, 2); d.Print(); return 0; }

运行结果:

若不初始化_ref、_n、_t,运行结果:

  • C++11⽀持在成员变量声明的位置给缺省值(和函数的缺省参数不同,不可混为一谈),这个缺省值主要是给没有显式在初始化列表初始化的成员变量使⽤的。
  • 未显式写初始化列表时:
  1. 若给了成员变量缺省值,初始化列表会⽤缺省值初始化成员变量;
  2. 若未给成员变量缺省值,对于内置类型成员变量,初始化列表是否初始化取决于编译器,不确定(随机值);
  3. 若未给成员变量缺省值,对于自定义类型成员,初始化列表调⽤该成员类型的默认构造,若没有默认构造则编译报错。

代码示例 1.3

#include<iostream> using namespace std; class Date { public: // 先走初始化列表,未显式实现,则使用缺省值初始化 Date(int year, int month, int day) { // 走完初始化列表,成员变量初始化完毕,再执行函数 // 判断一下初始化情况 if (nullptr == _ptr) { perror("malloc fail"); return; } // 用ptr初始化时动态申请的空间存储数据 _ptr[0] = 123; _ptr[1] = 456; _ptr[2] = 789; } ~Date() { free(_ptr); _ptr = nullptr; _year = _month = _day = 0; } void Print() { cout << _year << "年" << _month << "月" << _day << "日" << endl; cout << "_ptr[0]:" << _ptr[0] << endl; cout << "_ptr[1]:" << _ptr[1] << endl; cout << "_ptr[2]:" << _ptr[2] << endl; } private: // 成员变量声明的位置给缺省值,注意此处不是赋值 int _year = 1; int _month = 1; int _day = 1; int* _ptr = (int*)malloc(sizeof(int) * 3); // 缺省值也可以是一个表达式 }; int main() { // 传参调用带参构造,使用带参构造的初始化列表定义与初始化成员变量 Date d(2022, 2, 2); // 2022,2,2分别传给函数形参 d.Print(); return 0; }

运行结果:

需要注意,缺省值与缺省参数是两个完全不同的东西,我们可以从几点去区分:

  1. 缺省值在类中成员变量声明处,给成员变量,缺省参数在函数形参列表内,给形参,缺省参数不止可以在成员函数中,也可以在类外声明定义的函数中;
  2. 缺省值在初始化列表使用,未显式实现初始化列表时,用于初始化成员变量,缺省参数在函数形参列表使用,未指定传实参时,用于给形参一个默认值。
  3. 缺省值可以是值或表达式,缺省参数只能是值。

代码示例 1.4

#include<iostream> using namespace std; class Date { public: // 1、缺省值用于初始化列表,初始化列表未显式写的成员变量,则用缺省值初始化 // 2、缺省参数用于函数形参列表,给形参默认值。形参可用于初始化列表初始化成员变量, // 也可用于函数体内 Date(int year = 1, int month = 1, int day = 1) { cout << "初始化列表初始化后" << endl; cout << "_year:" << _year << endl; cout << "_month:" << _month << endl; cout << "_day:" << _day << endl; // 如果不主动使用形参赋值,缺省参数与成员变量就是无关的 // 缺省值才是与成员变量的初始化逻辑相关联的 _year = year; _month = month; _day = day; cout << "函数体内使用形参赋值后" << endl; cout << "_year:" << _year << endl; cout << "_month:" << _month << endl; cout << "_day:" << _day << endl; } private: // 成员变量声明的位置给缺省值,注意此处不是赋值 int _year = 2; int _month = 2; int _day; // 内置类型,不给缺省值,它的初始化是不确定的 }; int main() { // 调用全缺省构造 Date d; return 0; }

运行结果:

因此,成员变量的初始化,首先看初始化列表显式初始化了哪些成员变量,接着是未显式初始化的成员变量,再看缺省值,最后看构造函数体内是否赋值。

  • 在初始化列表初始化成员变量,成员变量的初始化顺序按声明顺序,跟成员在初始化列表出现的先后顺序⽆关。因此,建议声明顺序和初始化列表顺序保持⼀致。

下⾯程序的运⾏结果是什么?()

A. 输出1 1 B. 输出2 2 C. 编译报错

D. 输出1 随机值 E. 输出1 2 F. 输出2 1

#include<iostream> using namespace std; class A { public: A(int a) :_a1(a) ,_a2(_a1) {} void Print() { cout << _a1 << " " << _a2 << endl; } private: int _a2 = 2; int _a1 = 2; }; int main() { A aa(1); aa.Print(); }

分析回答:这题选D。首先,对象aa的空间包括它的成员变量的空间,在main函数栈帧创建时就开辟好了,再调用构造函数,随函数栈帧的创建,形参a的空间也开好了,接着走初始化列表,成员变量依次定义。因此,对象或变量的定义是没有任何问题的,不会发生未开空间未定义就使用的情况。这里主要是初始化问题,调用带参构造,实参1传给形参a。接着是初始化列表,根据声明顺序初始化成员变量。因此应该是先初始化_a2,再是_a1。于是_a2初始化时_a1是随机值,故_a2的值为随机值。_a1初始化用的形参a,也就是1,故_a1的值为1。

初始化列表总结如下:

2. 类型转换

类型转换的使用

  • C++⽀持内置类型隐式类型转换为类类型对象,需要有相关内置类型为参数的构造函数。

代码示例 2.1

#include<iostream> using namespace std; class A { public: // 带参构造 A(int a1) :_a1(a1) {} void Print() { cout << _a1 << endl; } private: int _a1; }; int main() { // 正常方式,定义对象aa1 A aa1(1); aa1.Print(); // 隐式类型转换方式,定义对象aa2:直接用参数赋值,需要有对应的带参构造支持 A aa2 = 2; aa2.Print(); // 注意与拷贝构造、赋值重载进行区分 // 隐式类型转换:直接使用参数赋值对象 // 拷贝构造:拷贝已存在的同类型对象,初始化aa2,如A aa2 = aa1 // 赋值重载:已存在对象之间的赋值,aa2还在定义与初始化 return 0; }

运行结果:

隐式类型转换在C语言中就已经存在,而C++将其延展到了类和对象。通过实现对应的带参构造函数,支持参数到对象的类型转换。

如代码示例2.1中,整型2被赋值给对象aa2,其实是调用了构造函数,将2传给了函数形参从而构建一个隐式的临时对象,再将该临时对象赋值给对象aa2,这里又要调用一个拷贝构造来初始化aa2。这就是参数能够赋值给对象的直接原因,这其中的关键角色是临时对象,相当于中转站,先用参数构造临时对象,再用临时对象通过拷贝构造初始化aa2。

将参数2换成2.2也可以,浮点数可以转换成整型,再调用带参构造构建临时对象,再调用拷贝构造即可。如果有匹配的形参为浮点型的构造函数,就不用先转换成整型。

运行结果(warning C4244: “参数”: 从“double”转换到“int”,可能丢失数据):

再将参数换成字符串"abc",编译报错,结果:

  • 语法意义上,类型转换必须要先构造⼀个临时对象,再⽤这个临时对象拷⻉构造初始化aa2。但实际情况下,出于性能考虑,编译器会对理论过程进行优化。当遇到这种连续构造(构造 + 拷⻉构造)的情况,可能忽略临时对象,直接用2构造aa2,省去拷贝构造。

我们可以用两句代码来验证一下,C++的强制类型中临时对象的存在,并尝试运行程序,查看一下在自己使用的平台下是如何优化的。

代码示例 2.2

#include<iostream> using namespace std; class A { public: // 带参构造 A(int a1) :_a1(a1) { cout << "A(int a1)" << endl; } // 拷贝构造 A(const A& aa) :_a1(aa._a1) { cout << "A(const A& aa)" << endl; } void Print() { cout << _a1 << endl; } private: int _a1; }; int main() { // 定义A类对象aa1 // 在构造函数和拷贝构造中添加一行被调用的信息输出,有优化就没有拷贝构造的调用信息 cout << "A aa1 = 3的调用情况:" << endl; A aa1 = 3; // 引用aa1 A& raa1 = aa1; // 直接引用参数3,3需要先强制类型转换成临时对象,临时对象有常性,必须用const引用 // 如果不使用const,使用普通引用A&,会报错,E0461:非常量引用的初始值必须为左值 cout << "const A& raa2 = 3的调用情况:" << endl; const A& raa2 = 3; return 0; }

运行结果:

类型转换的用途

类型转换主要用途在哪里呢?以函数调用为例,传实参时,如果形参是类类型对象,我们就必须传类类型对象,所以我们需要提前定义、构造并初始化一个有名对象。如果有类型转换,我们就可以直接传参数,参数强制类型转换成临时对象,一行函数调用的代码就可以搞定。

代码示例 2.3

#include<iostream> using namespace std; class A { public: // 带参构造 A(int a1) :_a1(a1) { cout << "A(int a1)" << endl; } // 拷贝构造 A(const A& aa) :_a1(aa._a1) { cout << "A(const A& aa)" << endl; } void Print() { cout << _a1 << endl; } private: int _a1; }; // 一个简化的栈 class Stack { public: // 压栈方法 void Push(const A& aa) // 形参可能接收临时对象,需要const引用 { // ... } private: int arr[10]; // 栈空间 int _top; // 栈顶 }; int main() { // 定义一个栈对象st Stack st; // 一般情况下,函数调用逻辑:先定义有名对象,再传对象给函数 A aa1 = 1; st.Push(aa1); // 有类型转换:直接传参,免去对象的定义,参数直接类型转换成匿名的临时对象 st.Push(1); return 0; }
  • 类类型的对象之间也可以隐式转换,只需要有对应的带参构造⽀持。也可以支持多参数的玩法,只需要为参数加一对花括号{}即可,同样需要有对应的带参构造支持。
  • 如果不想发生这种隐式类型转换,构造函数前⾯加关键字explicit就可以禁用。

代码示例 2.4

#include<iostream> using namespace std; class A { public: // 构造函数explicit就不再⽀持隐式类型转换 // explicit A(int a1) A(int a1) :_a1(a1) {} //explicit A(int a1, int a2) 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; int _a2 = 2; }; // 用B对象存储A对象中的两个成员变量之和 class B { public: // explicit B(const A& a) B(const A& a) // 形参需要接收一个A对象 :_b(a.Get()) // 获取A对象成员变量之和并初始化 {} void Print() { cout << _b << endl; } private: int _b = 0; }; int main() { // 多参数的玩法:有对应带参构造支持即可,参数在花括号内 // 括号内的参数依次传给构造函数的形参列表,通过构造函数构建临时对象 // 直接这样写是不行的,必须用花括号:A aa1 = 5, 6; A aa1 = { 5, 6 }; // 或 A aa1({ 5, 6 }); aa1.Print(); // 类类型对象之间的转换,A对象——>B对象 B bb1 = aa1; // 或 B bb1(aa1); bb1.Print(); return 0; }

运行结果:

3. static成员

  • ⽤static修饰的成员变量,称之为静态成员变量,静态成员变量⼀定要在类外进⾏初始化。
  • 一个类,有静态成员变量,这个类定义的所有对象,共享静态成员变量。静态成员变量不存在某个对象中,而是区别于普通成员变量,单独存放在静态区。
  • 静态成员变量不能在声明位置给缺省值初始化,因为缺省值是用于构造函数初始化列表的,静态成员变量不属于某个对象,其定义与初始化不⾛初始化列表。

代码示例 3.1

#include<iostream> using namespace std; class A { public: // 静态成员变量,在类⾥⾯声明 // 静态成员变量是不能用缺省值的,static int _scount = 1; // 缺省值用于初始化列表,静态成员变量不存在对象中,单独存在静态区,不走初始化列表 static int _scount; }; // 必须在类外初始化,不需要加static,声明已经加了 int A::_scount = 0; int main() { // sizeof计算类型或对象在内存中所占的字节数 // 验证说明,静态成员变量不在类和对象中,单独存放 A aa1; cout << sizeof(A) << endl; // 1个字节以占位标识 cout << sizeof(aa1) << endl; // 1个字节以占位标识 // A类定义的所有对象,aa1、aa2共享静态成员变量 A aa2; // aa1._scount、aa2._scount与A::_scount其实都是同一个scount cout << aa1._scount << " " << aa2._scount << " " << A::_scount << endl; return 0; }

运行结果:

其实,我们可以将静态成员变量理解为静态的全局变量,只是受类域和访问限定符的限制。静态成员变量和全局变量一样,运行程序后,在main函数之前就已经创建好了,到程序结束才生命周期结束。

静态成员变量受类域限制,类和对象中可以任意使用,但成员变量通常为私有,受访问限定符限制无法任意访问。因此,对于静态成员变量的类外访问问题,我们通常会借助静态成员函数,成员函数可以是公有,间接访问私有成员变量。

  • 静态成员也是类的成员,受public、protected、private访问限定符的限制。
  • 公有的情况下,突破类域就可以访问静态成员,可以通过类名::静态成员或者对象.静态成员来访问静态成员变量和静态成员函数。
  • ⽤static修饰的成员函数,称之为静态成员函数,静态成员函数没有 this 指针。
  • 静态成员函数中可以访问其他的静态成员,但是不能访问⾮静态的,因为没有this指针。
  • ⾮静态的成员函数可以任意访问类中的成员,无论是静态成员还是普通成员(成员包括成员变量和成员函数)。

代码示例 3.2

#include<iostream> using namespace std; class A { public: // 静态成员函数 static int GetACount() { // 需要牢记,静态成员函数没有this指针,成员函数是通过this指针来访问成员的 // 静态成员函数不可以访问非静态的成员变量或成员函数 // this->_a1 = 2; // 不可访问 // 通过公有的静态成员函数间接访问私有的静态成员变量 return _scount; } // 普通成员函数,任意访问类中成员 void func() { // 普通成员函数有this指针,可以访问其他成员变量或成员函数 _a1 = 2; cout << "void func()" << endl; cout << _a1 << endl; cout << _scount << endl; cout << GetACount() << endl; } private: int _a1 = 1; // 静态成员变量 static int _scount; }; int A::_scount = 0; int main() { // 可以用 类+域操作符:: 调用 cout << A::GetACount() << endl; // 也可以用当前类的对象调用 A aa1; cout << aa1.GetACount() << endl; aa1.func(); return 0; }

运行结果:

静态成员的作用是什么?我们知道,静态成员变量除了单独存放在静态区,具有更长的生命周期外,更重要的是它属于类的所有对象。因此,对象可以共享静态成员变量,存储一些共同数据。

以牛客网编程题——JZ64 求1+2+3+...+n 为例

不能循环,不能递归,但我们知道,类定义一个对象,会自动调用构造函数,而对象之间共享静态成员变量,利用于此,我们可以定义n个对象,进行计数,从而算出1到n之和。

// 求和类Sum,利用Sum的构造函数与静态成员变量求和 class Sum { public: Sum() { _ret += _i; // 求和 ++_i; // 下一个数 } static int GetRet() { // 返回求和总值 return _ret; } private: static int _i; // 当前第n个数 static int _ret; // n个数的总和 }; int Sum::_i = 1; // 初始化为1,从1开始到n int Sum::_ret = 0; // 初始化为0,从1开始求和 class Solution { public: // 1+2+...+n int Sum_Solution(int n) { // 需要编译器支持C99的变长数组 // 定义n个Sum类的数组元素对象 // Sum arr[n]; // 或者使用C++的方式,new创建对象 Sum* arr = new Sum[n]; // 返回求和的值 return Sum::GetRet(); } };

4. 友元函数

  • 友元是⼀种突破类访问限定符封装的⽅式,友元分为:友元函数和友元类。对于类外的非成员函数,或其他类,实现友元,需要在函数声明或者类声明前加friend,并且把友元声明放到类的⾥⾯。
  • 类外部的友元函数可访问类的私有和保护成员,友元函数仅仅是⼀种声明,不代表它是类的成员函数。
  • 友元函数可以在类定义的任何地⽅声明,不受类访问限定符限制。
  • ⼀个函数可以是多个类的友元函数。

代码示例 4.1

#include<iostream> using namespace std; // 前置声明,避免类A中使用了类B或其对象但不认识 class B; class A { // 友元声明——声明前加friend,并放到类中任意位置,通常放在最上面统一放置 friend void func(const A& aa, const B& bb); private: int _a1 = 1; int _a2 = 2; }; class B { private: int _b1 = 3; int _b2 = 4; // 友元声明——声明前加friend,并放到类中任意位置 // 声明为友元函数,但不代表成为成员函数 friend void func(const A& aa, const B& bb); }; // 类外的普通函数 void func(const A& aa, const B& bb) { // 需要在函数内访问A类、B类的私有成员变量,故需要声明为友元函数 // 通过友元,突破访问限定符的封装 cout << aa._a1 << endl; cout << bb._b1 << endl; } int main() { A aa; B bb; func(aa, bb); return 0; }

运行结果:

  • 对于友元类,它的成员函数都是另⼀个类的友元函数,都可以访问另⼀个类中的私有和保护成员。
  • 友元类的关系是单向的,不具有交换性,⽐如A类是B类的友元,但B类不是A类的友元。

代码示例 4.2

#include<iostream> using namespace std; class A { // 友元声明 friend class B; void funcB(const B& bb) { // 友元关系是单向的,B在A中声明为友元类,B中可以访问A的私有 // 但A没在B中声明为友元,A中不可以访问B的私有成员 // cout << bb._b1 << " " << bb._b2 << endl; } private: int _a1 = 1; int _a2 = 2; }; class B { public: // B类的成员函数中需要访问A类的私有成员变量 // 直接声明B为A的友元类,B类的成员函数就都是A类的友元函数 void func1(const A& aa) { cout << aa._a1 << endl; cout << _b1 << endl; } void func2(const A& aa) { cout << aa._a2 << endl; cout << _b2 << endl; } private: int _b1 = 3; int _b2 = 4; }; int main() { A aa; B bb; bb.func1(aa); bb.func2(aa); return 0; }

运行结果:

  • 友元类的关系不能传递,如果A是B的友元,B是C的友元,但是A不是C的友元。(就像我们不一定认识我们朋友的朋友)
  • 有时提供了便利。但是友元会增加耦合度,破坏了封装,所以友元不宜多⽤。但该用的时候大胆用。

5. 内部类

  • 可以在类内部定义另⼀个类,内部的这个类叫做内部类。内部类是⼀个独⽴的类,跟定义在全局相⽐,它只是受外部类类域限制和访问限定符限制,所以外部类定义的对象中不包含内部类及其成员变量。
  • 内部类默认是外部类的友元类,内部类可以访问外部类的私有成员。
  • 内部类本质也是⼀种封装,当A类跟B类紧密关联,A类实现出来主要就是给B类使⽤,那么可以考虑把A设计为B的内部类,如果放到private/protected位置,那么A类就是B类的专属内部类,其他地⽅都⽤不了。

代码示例 5.1

#include<iostream> using namespace std; // Sum类就是给Solution类使用的,因此可以将Sum设为Solution的内部类 class Solution { // class没有访问限定符默认为私有 // 私有的内部类,专属于外部类,默认为外部类的友元类 class Sum { public: Sum() { // 成员变量放在外部类,内部类可以直接使用 _ret += _i; ++_i; } // 无需get函数访问私有成员变量了,私有成员变量在外部类 /*static int GetRet() { return _ret; }*/ }; static int _i; static int _ret; public: int Sum_Solution(int n) { // 可以直接访问,原本Sum类的,现在Solution的私有成员变量了 Sum* arr = new Sum[n]; return _ret; // return Sum::GetRet(); } }; int Solution::_i = 1; int Solution::_ret = 0; int main() { // 内部类是独立的类,外部类对象不存储内部类,及其成员变量 // 计算Solution类对象的大小时,不包括Sum类的大小 Solution s; // 内部类不仅受类域影响,同样受访问限定符影响,类外其他部分不可访问私有内部类 // Solution::Sum s; }

6. 匿名对象

  • 定义对象时,可以省去对象名,即定义一个匿名对象。相⽐之前,正常定义的,类型+对象名+(实参)定义出来的叫有名对象。
  • 匿名对象⽣命周期只在当前⼀⾏,用完下一行立马销毁。⼀般,临时定义⼀个对象当前⽤⼀下即可,就可以定义匿名对象。

代码示例 6.1

#include<iostream> using namespace std; class A { public: // 默认构造——全缺省构造 A(int a = 0) :_a(a) { cout << "A(int a = 0)" << endl; } // 析构 ~A() { cout << "~A()" << endl; } private: int _a; }; class Solution { public: int Sum_Solution(int n) { //... return n; } }; int main() { // 定义对象,使用默认构造初始化可以不用传参,如下 // 调用一次构造,程序结束再调用一次析构 A aa1; // 但不能这么定义对象,因为编译器⽆法识别下⾯是⼀个函数声明,还是对象定义,不传参不用写括号 // A aa1(); // 但可以这样定义匿名对象,匿名对象可以不⽤取名字 // 但它的⽣命周期只有⼀⾏,调用一次构造后,下一行,立马销毁调用析构 A(); // 也可以给匿名对象传参 A(1); // 一次构造,一次析构 // 正常定义的有名对象 A aa2(2); // 一次构造,程序结束一次析构 // 匿名对象在这样的场景下就很好⽤,当然还有⼀些其他使⽤场景,这个我们以后遇到了再说 // 一次构造,一次析构 Solution().Sum_Solution(10); // 直接用 类类型+括号() 临时定义一个匿名对象调用成员函数 // 正常调用:定义一个有名对象,再调用成员函数 // Solution s; // s.Sum_Solution(10); // 程序结束,两个有名对象,两次析构 return 0; }

运行结果:


需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询