C++ 类和对象(下):初始化列表、static、友元与匿名对象全解


🌟 个人主页噜噜大王

❄️ 个人专栏《C语言》 《C++》

🏆 道阻且长,行则将至


🎨噜噜大王的简介:


文章目录


前言

在 C++ 面向对象编程中,类和对象的进阶特性是构建高效、安全代码的核心。上篇我们聊了类的基础定义与构造析构,今天深入初始化列表、类型转换、static 成员、友元、内部类、匿名对象六大核心知识点,结合代码案例与易错点解析,帮你彻底吃透这些高频考点。


一、构造函数初始化列表:高效初始化的关键

构造函数初始化列表是 C++ 推荐的成员初始化方式,相比函数体内赋值,它直接在成员定义阶段(初始化列表是成员变量定义的地方)完成初始化,效率更高,且能解决特殊成员的初始化问题。

1. 基本语法

以冒号:开头逗号分隔成员初始化项,格式:

cpp 复制代码
类名(参数) 
: 成员1(值1)
, 成员2(值2) 
{ 函数体 }

2. 必须用初始化列表的 3 种成员

  • 引用成员:引用必须在定义时绑定变量,无法在函数体内赋值
  • const 成员:常量成员只能初始化,不能赋值
  • 无默认构造的自定义类型成员:无法默认构造,必须显式初始化

3. 核心规则(易错点)

初始化顺序 :按类中声明顺序初始化,和列表书写顺序无关(建议两者保持一致,避免 bug)

默认缺省值 :C++11 支持成员声明时给缺省值,未在列表显式初始化的成员会使用该值

强制初始化 :所有成员都会走初始化列表,未显式初始化的内置类型可能是随机值,自定义类型会调用默认构造(无则报错)

构造函数的初始化列表:每一个构造函数都无论是否显示写初始化列表,每个构造函数都有初始化列表,所以构造函数和析构函数一样无论显示写不写构造函数,都会去调用自定义类型成员变量的默认构造函数和析构函数。

4. 经典例题解析

cpp 复制代码
class A {
public:
    A(int a) 
    : _a1(a)
    , _a2(_a1) 
    {}
    void Print() 
    { 
        cout << _a1 << " " << _a2 << endl; 
    }
private:
    int _a2 = 2;  // 声明顺序:_a2在前
    int _a1 = 2;  // _a1在后
};
int main() {
    A aa(1);
    aa.Print();  // 输出:1 随机值(答案D)
}

解析:成员按声明顺序初始化,先初始化_a2(此时_a1未初始化,是随机值),再初始化_a1=1。

5.构造函数初始化顺序

初始化列表和默认缺省值只走一个(有初始化列标就走初始化列表,没有初始化列标就走默认缺省值),然后再去走构造函数函数体内的。

二、类型转换:隐式转换与 explicit 限制

C++ 支持内置类型→类类型类类型→类类型的隐式转换,由构造函数控制,explicit可禁止隐式转换。

1. 隐式转换规则

  • 单参数构造函数:支持内置类型隐式转类对象
  • 多参数构造函数:C++11 后支持{}列表隐式转换
  • 拷贝构造函数:支持同类对象隐式转换

2. explicit 关键字

在构造函数前加explicit,仅保留显式初始化,禁止隐式转换:

cpp 复制代码
class A {
public:
    explicit A(int a1) : _a1(a1) {}  // 禁止隐式转换
private:
    int _a1;
};
int main() {
    A aa1 = 1;  // 编译报错!隐式转换被禁止
    A aa2(1);   // 正确,显式初始化
}

3. 代码演示

cpp 复制代码
#include<iostream>
using namespace std;
class A
{
public:
	//explicit A(int a = 0)
	A(int a = 0)
	{
		_a1 = a;
	}

	A(const A& aa)
	{
		_a1 = aa._a1;
	}

	A(int a1, int a2)
		:_a1(a1)
		, _a2(a2)
	{}

	void Print() {
		cout << _a1 << " " << _a2 << endl;
	}
private:
	int _a1;
	int _a2;
};

class Stack
{
public:
	void Push(const A& aa)
	{
		 //...
	}
private:
	A _arr[10];
	int _top;
};

int main()
{
	A aa1(1);
	aa1.Print();

	// 隐式类型转换
	// 2构造一个A的临时对象,再用这个临时对象拷贝构造aa2
	// 编译器遇到连续构造+拷贝构造->优化为直接构造
	A aa2 = 2;
	aa2.Print();

	A& raa1 = aa2;
	const A& raa2 = 2;

	int i = 1;
	double d = i;
	const double& rd = i;

	Stack st;

	A aa3(3);
	st.Push(aa3);

	st.Push(3);

	// C++11
	A aa5 = { 1, 1 };
	const A& raa6 = { 2,2 };
	st.Push(aa5);
	st.Push({2,2});

	return 0;
}

这同时也体现了const做形参的好处

三、static 成员:属于类,而非对象

用static修饰的成员(变量 / 函数),归类所有,所有对象共享,存储在静态区,无 this 指针

1. static 成员变量

  • 声明:类内声明,属于类域
  • 初始化:必须类外初始化(不能在声明处给缺省值,因为缺省值是给构造函数初始化列表的,静态成员变量不属于某个对象,不走构造函数初始化列表。)
  • 访问:类名::变量名 或 对象.变量名
  • 限制:静态成员变量也是类的成员,受public、protected、private 访问限定符的限制。

2. static 成员函数

  • 无 this 指针:只能访问 static 成员,不能访问非 static 成员
  • 访问:类名::函数名() 或 对象.函数名()
  • 限制:静态成员函数也是类的成员,受public、protected、private 访问限定符的限制。
  • 类内访问:非静态的成员函数,可以访问任意的静态成员变量和静态成员函数。

3. 实战案例:统计对象个数

cpp 复制代码
class A {
public:
    A() 
    { 
        ++_scount; 
    }
    A(const A&) 
    {
        ++_scount; 
    }
    static int GetCount() 
    {
        return _scount; 
    }
private:
    static int _scount;  // 类内声明
};
int A::_scount = 0;  // 类外初始化
int main() {
    A a1, a2;
    A a3(a1);
    cout << A::GetCount();  // 输出:3
}

四、友元:打破封装,谨慎使用

友元(friend)用于突破类的访问权限,让外部函数 / 类访问私有成员,分友元函数友元类

1. 友元函数

  • 不是类成员函数,无 this 指针,友元函数仅仅是⼀种声明,他不是类的成员函数。
  • 可访问类的私有 / 保护成员
  • 声明位置任意,不受访问限定符限制
cpp 复制代码
class B;  // 前置声明
class A {
    friend void func(const A&, const B&);  // 友元声明
private:
    int _a = 1;
};
class B {
    friend void func(const A&, const B&);
private:
    int _b = 2;
};
void func(const A& a, const B& b) {
    cout << a._a << b._b;  // 可访问私有成员
}

2. 友元类

  • 友元类的所有成员函数都是当前类的友元(在A中friendB,B类中就可以访问A中的所有的成员)
  • 单向性:A 是 B 的友元,B 不是 A 的友元
  • 无传递性:A 友元 B,B 友元 C,A 不是 C 的友元

3. 代码演示

cpp 复制代码
using namespace std;
class A
{
public:	// 友元声明
	friend class B;
	void print() const
	{
		cout << "哈哈哈" << endl;
	}
private:
	int _a1 = 1;
	int _a2 = 2;
};
class B
{
public:
	void func1(const A& aa)
	{
		aa.print();
		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;
}

注意事项 :友元破坏封装、增加耦合度,仅在必要场景(如运算符重载)使用,不可滥用。

五、内部类:嵌套定义,专属关联

定义在另一个类内部的类,称为内部类,是独立类,仅受外部类域和访问权限限制

核心特性

  1. 内部类默认是外部类的友元类,可访问外部类私有成员
  2. 外部类不能直接访问内部类私有成员
  3. 内部类是⼀个独立的类,跟定义在全局相比,他只是受外部类类域限制和访问限定符限制,所以外部类定义的对象中不包含内部类。
  4. 内部类仅在外部类域内可见,private 内部类为外部类专属
cpp 复制代码
using namespace std;
class A
{
private:
	static int _k;
	int _h = 1;
public:
	class B // B默认就是A的友元
	{
	public:
		void foo(const A& a)
		{
			cout << _k << endl;
			//OK
			cout << a._h << endl;
			//OK
		}
	private:
		int _b1;
	};
	void test()
	{
		//_b1 = 2;err外部类不可以直接访问内部类,受访问限定符约束
	}
};
int A::_k = 1;
int main()
{
	cout << sizeof(A) << endl;//4
	A::B b;
	A aa;
	b.foo(aa);//1 1
	return 0;
}

六、匿名对象:临时使用,用完即销毁

格式:类名(实参)无对象名,生命周期仅当前一行,适合临时调用成员函数。

特性与用法

  • 生命周期短:创建后立即销毁
  • 节省空间:无需定义有名对象,临时场景高效
  • 常见场景:类名().成员函数()
cpp 复制代码
using namespace std;
class A {
public:
    A()
    {
        cout << "创建";
    }
    void func()
    {
        cout << "666" << endl;
    }
    ~A()
    {
        cout << "销毁";
    }
};
int main() {
    A();  // 匿名对象:输出"创建销毁"
    A().func();  // 临时调用成员函数
}

七、对象拷贝的编译器优化

现代编译器会优化连续构造 + 拷贝构造 ,减少拷贝开销(如A aa = 1直接构造,不生成临时对象)。

cpp 复制代码
using namespace std;
class A
{
public:
	A(int a = 0)
		:_a1(a)
	{
		cout << "A(int a)" << endl;
	}
	A(const A& aa)
		:_a1(aa._a1)
	{
		cout << "A(const A& aa)" << endl;
	}
	A& operator=(const A& aa)
	{
		cout << "A& operator=(const A& aa)" << endl;
		if (this != &aa)
		{
			_a1 = aa._a1;
		}
		return *this;
	}
	~A()
	{
		cout << "~A()" << endl;
	}
private:
	int _a1 = 1;
};
void f1(A aa)
{
}
A f2()
{
	A aa;
	return aa;
}
int main()
{
	// 隐式类型,连续构造+拷⻉构造->优化为直接构造
	f1(1);
	// ⼀个表达式中,连续构造+拷⻉构造->优化为⼀个构造
	f1(A(2));
	//返回时⼀个表达式中,连续拷⻉构造+拷⻉构造->优化⼀个拷⻉构造 (vs2019 debug)
	A aa1 = f2();
	return 0;
}

优化场景

  1. 隐式类型转换:f1(1)直接构造,跳过拷贝
  2. 匿名对象传参:f1(A(2))合并为一次构造
  3. 返回值优化:A aa = f2()直接构造,无临时对象

linux下可以将下面代码拷贝到test.cpp文件,编译时用 g++ test.cpp -fno-elide-constructors 的方式关闭构造相关的优化。

具体怎么优化要取决于编译器,不同的编译器上优化方式会不同


总结

C++ 类和对象的进阶特性,核心是初始化列表控效率、static 管共享、友元破封装、内部类强关联、匿名对象省空间。这些知识点既是面试高频考点,也是编写高质量 C++ 代码的基础,建议结合案例多练,吃透细节与易错点。

以上就是该篇博客的内容了,如果内容存在不足,请大佬多多包涵并不吝赐教,会在写出优秀好文的路上努力拼搏达,感谢支持!!!

一键三连。[1](#1)感谢佬们的支持😄


  1. 点赞,关注,收藏qwq感谢佬佬们 ↩︎
相关推荐
lDevinl1 小时前
【无标题】
数据结构·c++·青少年编程
zincsweet1 小时前
System V 共享内存:原理剖析、代码架构分析与双端通信实战
linux·c++
wunaiqiezixin10 小时前
如何在C++中创建和管理线程
c++
雪度娃娃10 小时前
转向现代C++——在意为改写的函数添加 override
开发语言·c++
王老师青少年编程10 小时前
csp信奥赛C++高频考点专项训练之前缀和&差分 --【一维差分】:[NOIP 2018 提高组] 铺设道路
c++·前缀和·差分·csp·高频考点·信奥赛·铺设道路
星马梦缘11 小时前
aaaaa
数据结构·c++·算法
喵星人工作室12 小时前
C++火影忍者1.1.2
开发语言·c++
basketball61612 小时前
C++ 中的 ptrdiff_t 详解
开发语言·c++
wunaiqiezixin12 小时前
互斥锁与自旋锁的区别
c++