《C++深度解构04》类和对象(下)——类型转换、static、友元与编译器优化

目录

📌 相关专栏与源码导航:

一.类型转换

1.内置类型转自定义

类型转换就是把一个类型的数据转换成另一个类型的数据。

cpp 复制代码
class Data
{
public:
	Data(int y )
		:_year(y)
	{ }

private:
	int _year;
};

int main()
{
	Data d1 = 2026;
	return 0;
}

Data d1(2026); //正常传参

Data d2 = 2026;/ /隐式类型转换

仔细观察上面两种实例化对象的不同点,d1的实例化是实参的传入,而d2是直接用int类型数据2026直接创建Data类类型的对象。

虽然看起来像是用整型去创建Date,但是实际上是隐式类型转换,思路上我们可以理解为,注先利用int类型整型创建了一个Date类类型临时对象,然后将临时对象拷贝给d2

|-----------------------------------------------------------------|
| **⚠️ 注意:**不一定会进行临时对象的拷贝,后期的编译器优化问题可能会直接实例化对象,而不产生临时对象,也不会产生拷贝问题 |

2.自定义转换内置

自定义转函数类型

cpp 复制代码
class Data
{
public:
	operator int()
	{
		return _year;
	}

private:
	int _year = 2026;
};

int main()
{
	Data d1;
	int year = d1;
	cout << year;
	return 0;
}

通常重载操作符的模板是 operator 操作符( ),但是这里我们是在定义一个类型转换函数

operator后面的int不是操作符,而是目标类型,需要将Data类型对象转换为int时,调用这个函数

3.explicit关键字

对于隐式类型转换,是编译器自动识别并执行的指令,那么我们如果不想让编译器自动转换呢?

例如:

被explicit修饰后的构造函数,表明无需编译器进行隐式类型转换,故我们直接利用整形构造会编译出错。 \colorbox{#FFEAEA}{\color{#C22828}\text{被explicit修饰后的构造函数,表明无需编译器进行隐式类型转换,故我们直接利用整形构造会编译出错。}} 被explicit修饰后的构造函数,表明无需编译器进行隐式类型转换,故我们直接利用整形构造会编译出错。


二.static成员

1.内存与归属

我们先来看一个例子

cpp 复制代码
class Data
{
public:
	Data(int year = 2026)
		:_year(year)
	{ }

private:
	int _year;
	static int _time;//👍👍👍声明一个static成员变量
};

int main()
{
	Data d1;
	cout << sizeof(d1) << endl;
	return 0;
}

根据内存对齐规则,这里对象d1的大小应该是8才对,为什么却只有4个字符大小呢?

这边引出了我们static修饰成员的特性

static修饰的成员变量为所有该类的全部对象所共享,不占用单个对象的内存,并且static成员放在静态区

2.声明与定义

tatic成员变量既然不存在于类内,那他的声明和定义该怎么去处理呢?

虽然不占用类的内存 ,但是他被称为static成员变量,所以他也需要在类内声明,被类域所限制使用范围。

static成员变量的定义与普通成员变量不同,普通成员变量走的是构造函数,或者通过初始化列表进行成员变量的定义。

注意: \colorbox{#E53935}{\color{#FFFFFF}\text{注意: }} 注意:

static成员变量需要在类外进行定义(一般情况都是在全局进行定义)

cpp 复制代码
class Data
{
public:
	Data(int year = 2026)
		:_year(year)
	{ }

private:
	int _year;
	static int _time;
};

int Data::_time = 0;//注意标明命名空间

int main()
{
	Data d1;
	return 0;
}

但是为什么private的成员变量可以在类外被使用呢?

这里便是一个特例,只有static成员变量可以在定义时期被使用

特例:

通常情况static成员变量必须在类外定义,但是有一种特殊情况可以认为在类内声明时直接被定义了

cpp 复制代码
class Data
{
public:
	Data(int year = 2026)
		:_year(year)
	{ }

private:
	int _year;
	inline static int _time = 1;
};


int main()
{
	Data d1;
	return 0;
}

注意: \colorbox{#E53935}{\color{#FFFFFF}\text{注意: }} 注意:

这里类型必须是inline static int 才可以在这里定义,除这一种情况任何情况 static都必须在类外进行定义,并且编译器需要支持C++17标准才可以在类内进行定义


3.static成员函数及访问权限

在类和对象中的讲解中我们知道,同一个类实例化出的多个对象,在调用成员函数时是利用隐式传递this指针进行形参传递

核心避坑 : \colorbox{#E53935}{\color{#FFFFFF}\text{ 核心避坑 :}} 核心避坑 :

被static修饰的成员,被称为静态成员(函数 /变量),静态成员函数的特性便是没有隐式传递的this指针,没有this指针无法访问非静态成员,但是可以访问静态成员变量

cpp 复制代码
class Data
{
public:
	Data(int year = 2026)
		:_year(year)
	{
	}
	static void print()
	{
		cout << _time << endl;//✅静态成员可以访问静态成员(变量/函数),但是无法访问非静态成员
		cout << this->_year << endl;//❌形参没有隐式传递的this指针所以无法访问
	}
private:
	int _year;
	static int _time;
};
int Data::_time = 1;

思考:为什么static成员函数没有this指针 \colorbox{#FFEAEA}{\color{#C22828}\text{思考:为什么static成员函数没有this指针}} 思考:为什么static成员函数没有this指针

普通成员函数虽不占用对象的内存,但是他属于对象,而static成员不归属于任何一个对象,只是被类的域所限制了使用范围,所以没有this指针

4.使用环境

核心: \colorbox{#FFEAEA}{\color{#C22828}\text{核心:}} 核心:

当某一个东西不单独属于某个对象,而是属于整个类时考虑使用static修饰

例子: \colorbox{#FFEAEA}{\color{#C22828}\text{例子:}} 例子:

这里我们考虑利用构造函数的次数进行计数,那么就必须要有一个能统计全局的计数器,不能仅仅是存在某个对象中,这样的化每实例化一个对象都会产生一个自己的计数器,而不是统计整个过程的计数器,所以这里我们需要static成员进行记录

三.友元

1.为什么使用友元

给定一个场景,我们在类外想访问类成员变量,但是类成员会被private、protected等限制访问,这时我们想要访问成员变量只能定义一个成员函数来访问并返回成员变量。

cpp 复制代码
class Data
{
public:
	Data(int year = 2026)
		:_year(year)
	{ }

private:
	int _year;
};

解决方法: \colorbox{#FFEAEA}{\color{#C22828}\text{解决方法:}} 解决方法:

第一种方法:

将成员变量的访问限定符改成public(共有)

cpp 复制代码
public:
	int _year;
Data d1;
int i = d1._year;
d1._year = 10;//此时可以任意更改对象的变量

虽然在类外可以任意访问了,但是安全性降低,我们将其限定为pirvate就是为了防止有人篡改我们的数据,这时岂不是将秘密公之于众?!

第二种方法:

cpp 复制代码
	int year()
	{
		return _year;
	}

Date d1;
int i = d1.year();

因为成员函数在类内可以访问成员变量,所以可以声明一个成员函数让他访问成员变量,然后再将其返回,既做到了保护成员变量的安全,还得到了成员变量的值。

但是我们希望一个函数不是成员函数,但是又可以访问类内成员变量,这时友元便发挥了作用 \colorbox{#FFEAEA}{\color{#C22828}\text{但是我们希望一个函数不是成员函数,但是又可以访问类内成员变量,这时友元便发挥了作用}} 但是我们希望一个函数不是成员函数,但是又可以访问类内成员变量,这时友元便发挥了作用

2.友元函数

像上面的情况,不是类内的成员函数,普通的函数能不能也访问到该怎么做呢?

因为_year被private所限制,无法访问。

形象形容: \colorbox{#FFEAEA}{\color{#C22828}\text{形象形容:}} 形象形容:

d1是一个陌生人,他的家里有游戏机,print函数是你,你可以直接去人家家里玩游戏机吗,肯定是不可以的,那么你和他成为朋友被他邀请到家里不就可以玩游戏机了吗,这便是友元函数的概念。

cpp 复制代码
//类中声明
friend void print(Data& d);

//类外函数
void print(Data& d)
{
	cout << d._year << endl;
}

通过friend修饰将类外部函数的声明写在类内,这样类外函数也可以访问被访问限定符限定的成员变量或者函数了。

错误示范: \colorbox{#E53935}{\color{#FFFFFF}\text{ 错误示范: }} 错误示范:

我们想将print函数作为友元函数去访问类内成员,但是这样写的话是在主函数中直接访问成员变量,然后作为实参传递给print函数,而不是通过友元函数print去访问得到的。

3.友元类

友元函数,是类外函数访问类内成员的方法,可以打破访问限定符的枷锁,那么可不可以将类作为友元呢?

答案是当然可以的。

示例:

cpp 复制代码
class Data
{
public:
	
	friend class Time;

	Data(int year = 2026)
		:_year(year)
	{
	}

private:
	int _year;
};


class Time
{
public:
	Time(int time = 1)
		:_time(time)
	{}

	void func(Data& d)
	{
		cout << d._year << endl;
	}

private:
	int _time;
};

int main()
{
	Data d1;
	Time t1;
	t1.func(d1);
	return 0;
}

将类的声明放在另一个类中最为友元类,这样就可以访问我们需要的成员了

4.友元特性

①友元函数不是类的成员函数

cpp 复制代码
Data
{
friend void print(Data& d);
private:
		int _year;
}

Date d1;
cout << sizeof(d1) << endl;

输出结果为4,证明友元函数不占用对象的空间

②友元关系不可以传递

cpp 复制代码
class A
{
private:
    int _a = 10;

    friend class B;  // B 是 A 的友元
};

class B
{
public:
    void func(A& aa)
    {
        cout << aa._a << endl;  // ✅ B 可以访问 A 的 private
    }

    friend class C;  // C 是 B 的友元
};

class C
{
public:
    void func(A& aa)
    {
        cout << aa._a << endl;  // ❌ 错误!
    }

private:
    int _c = 30;
};

B是A的友元函数,C是B的友元函数,不能推出C是A的友元函数,所以不能访问

③友元关系具有单向性

cpp 复制代码
class A
{
private:
    int _a = 10;

    friend class B;

    void func(B& bb)
    {
        cout << bb._b << endl;//❌友元不具有双向性
    }

};

class B
{
public:
    void func(A& aa)
    {
        cout << aa._a << endl; 
    }

private:
    int _b = 20;
};

友元关系并不是双向的,B作为A的友元时,只能允许B访问A的成员变量,不同时允许A访问B的成员变量。

总结: \colorbox{#FFEAEA}{\color{#C22828}\text{总结:}} 总结:


四.内部类

1.内部类定义

通过词义我们就可以理解到内部类,便是类种类(在类中定义类)

cpp 复制代码
class A
{
public:

	class B//内部类
	{
	private:
		int _b;
	};

private:
	int _a;
};

2.访问权限

内部类存在于类内,访问权限会有什么变化吗?

看一段代码

cpp 复制代码
class Data
{
public:
	Data(int year = 10)
		:_year(year)
	{ }
	//内部类
	class Time
	{
	public:
		Time(int time = 1)
			:_time(time)
		{ }
		
		void print(Data& d)
		{
			cout << _time << endl;
			cout << d._year << endl;
		}

	private:
		int _time;
	};

private:
	int _year;
};

int main()
{
	Data d1;
	Data::Time t;
	t.print(d1);
	return 0;
}

1.类可以限定作用域被称为类域,那么存在于类内的类想要实例化时,一定要标明作用域如Data::Time t,因为内部类本质上还是一个独立的类,只是被外部类域限定,不作为外部类成员,所以在实例化外部类时不会同时实例化出一个内部类,只是需要实例化内部类时要加上域名

2.内部类默认为友元类,可以访问外部类的成员

3.外部类无法访问内部类成员,友元不具有双向性

五.匿名对象

1.匿名对象定义

定义形式: \colorbox{#FFEAEA}{\color{#C22828}\text{定义形式:}} 定义形式:

匿名对象不指定对象名(即实例化一个对象不给它名字),直接使用类加括号实例化对象如 :func( )

cpp 复制代码
class Data
{
public:

	Data(int year = 2026)
		:_year(year)
	{}

	void func()
	{
		cout << _year << endl;
	}

private:
	int _year;
};

int main()
{
	Data().func();
	return 0;
}

2.适用范围及生命周期

适用范围: \colorbox{#FFEAEA}{\color{#C22828}\text{适用范围:}} 适用范围:

匿名对象的使用适用于仅需要实例化对象传入参数而后面不使用时使用,并且匿名对象只能在当前.cpp文件使用,不能放在其他文件中使用

生命周期: \colorbox{#FFEAEA}{\color{#C22828}\text{生命周期:}} 生命周期:

匿名空间的生命周期仅存在于这一句语句,语句结束则自动释放

匿名对象实例化成功后,这句语句结束,匿名对象的生命周期也随之结束。

六.编译器优化

cpp 复制代码
class Data
{
public:

	Data(int year)
		:_year(year)
	{ }

private:
	int _year;
};

int main()
{
	Data d1 = 2026;
	return 0;
}

情况一: \colorbox{#FFEAEA}{\color{#C22828}\text{情况一:}} 情况一:

隐式类型转换实例化对象时,会先利用20调用构造函数创造一个临时对象,然后调用拷贝构造函数将临时对象拷贝给d1,这样就完成了对象的实例化

实例化过程为:

连续的一行:构造 ➡️ 拷贝构造 🟰 完成实例化

但是编译器认为这样的操作很麻烦,所以编译器直接将其优化合并为一次构造,如(A aa = 1)

情况二: \colorbox{#FFEAEA}{\color{#C22828}\text{情况二:}} 情况二:

接收对象返回优化(RVO / NRVO),函数外接收返回值时(return 局部对象),常常会返回一个临时变量,然后再拷贝给接收变量

编译器直接合并成一次构造,0次拷贝


无法优化的场景: \colorbox{#E53935}{\color{#FFFFFF}\text{ 无法优化的场景:}} 无法优化的场景:

场景:

预先定义好的对象接收赋值(A ret; ret = f();) \colorbox{#FFEAEA}{\color{#C22828}\text{预先定义好的对象接收赋值(A ret; ret = f();)}} 预先定义好的对象接收赋值(A ret; ret = f();)

第一步实例化对象时,会调用一次构造函数,然后接收返回值时,第二部因为返回的是临时对象,所以还会再调用一次拷贝构造函数,构造和拷贝构造分为两步走无法被优化。

注意:具体的优化行为依据编译器,不同编译器的优化方式也不同 \colorbox{#FFEAEA}{\color{#C22828}\text{注意:具体的优化行为依据编译器,不同编译器的优化方式也不同}} 注意:具体的优化行为依据编译器,不同编译器的优化方式也不同

到此,类和对象章节完结。


"不必急于证明自己,沉下心走好脚下的每一步,时间自会给出答案。"
Crafted with passion by Hhy
当前更新时间:{{ 2026 / 9 / 19 }} | 此博文系博主学习复盘,欢迎指正


本文专栏由 Hhy 维护。如果这篇文章曾带给你启发,请考虑关注我的 CSDN 博客或 GitHub 仓库以获取最新技术分享。

相关推荐
传奇开心果编程1 小时前
【Flutter入门练中学】第1课:从零开始
学习·flutter·ui
qq_199886871 小时前
第7板块·第1节:通用算子分类与设计模式
c++·人工智能·gpu算力·cuda
6Hzlia3 小时前
【Classic 150 刷题计划】 LeetCode 205. 同构字符串 | C++ 双向绑定与哈希表映射
c++·算法·leetcode
神仙别闹9 小时前
基于 C++ 实现(控制台)学生成绩管理系统
开发语言·c++
会周易的程序员9 小时前
5 节点边缘冗余方案(上):基于 aiRaft 的物联网高可用控制面设计
c++·分布式·物联网·raft·iot·共识
小弥儿11 小时前
GitHub今日热榜 | 2026-09-18:腾讯双项目上榜
学习·开源
free-elcmacom12 小时前
C语言字符指针与字符串字面量——从一道“剑指 Offer”笔试题说起
c语言·visual studio
qq_1998868712 小时前
第6板块·第4节:构建系统与多文件项目
c++·人工智能·gpu算力
知识分享小能手13 小时前
深度学习学习教程,从入门到精通,深度生成模型 —— 知识点详解与代码实现(20)
人工智能·深度学习·学习