【c++】类和对象(七)

Alt

🔥个人主页Quitecoder

🔥专栏c++笔记仓

Alt

朋友们大家好,本篇文章来到类和对象的最后一部分

目录

  • 1.static成员
    • 1.1特性
  • 2.友元
    • 2.1引入:<<和>>的重载
    • 2.2友元函数
    • 2.3友元类
  • 3.内部类
  • 4.匿名对象
  • 5.拷贝对象时的一些编译器优化

1.static成员

声明为static的类成员称为类的静态成员,用static修饰的成员变量,称之为静态成员变量;用static修饰的成员函数,称之为静态成员函数静态成员变量一定要在类外进行初始化

例题:统计A类型创造了多少个对象

class A
{
public:
	A()
	{}

	A(const A& a)
	{

	}
private:
};
A Func()
{
	A aa;
	return aa;
}
int main()
{
	A aa1;
	A aa2;
	Func();
	return 0;
}

我们的方法之一就是统计构造函数调用了多少次,我们就需要全局变量

int n = 0;
class A
{
public:
	A()
	{
		++n;
	}

	A(const A& a)
	{
		++n;
	}
private:
};
A Func()
{
	A aa;
	return aa;
}
int main()
{
	A aa1;
	A aa2;
	Func();
	cout << n << endl;
	return 0;
}

但是这个n是不好控制的,能不能把n封装到类里面,避免别人随意修改呢?

class A
{
public:
	A()
	{
		++n;
	}

	A(const A& a)
	{
		++n;
	}
private:
 int n =0;
};

但是由于对象的不同,n就不是同一个n,那么如果我想要使同一个n++呢?

这里就需要设置静态变量

private:
	static int n;

这个n就不属于某一个对象,而是属于所有对象,属于整个类,所以它的初始化不能放在初始化列表执行,那么它的初始化应该在哪里呢?所以需要在类外面定义:

class A
{
public:
	A()
	{
		++n;
	}

	A(const A& a)
	{
		++n;
	}
private:
	static int n;
};
int A::n = 0;

这个n则受到类的限制,无法随意访问,如果想访问n,有几种办法:

  • 方法一,将n改为公有,但是破坏了封装性,不建议
  • 方法二,get函数
class A
{
public:
	A()
	{
		++n;
	}

	A(const A& a)
	{
		++n;
	}
	static int Getn()
	{
		return n;
	}
private:
	static int n;
};

讲解一下这里的静态成员函数static int Getn()

静态成员函数在类中有特殊的作用和行为。在上面的代码示例中,Getn 函数是一个静态成员函数,它的主要特点和用法包括:

  1. 类范围内的函数:静态成员函数属于整个类,**而不是类的某个特定对象。**因此,它不能访问类的非静态成员变量或成员函数,因为这些成员需要一个特定的对象实例来确定它们的上下文

  2. 无需对象实例可以在没有类的对象实例的情况下调用静态成员函数。这是因为静态成员函数不依赖于任何特定对象的状态。在上面的例子中,A::Getn() 可以在没有创建 A 类对象的情况下调用,它提供了一种访问类静态成员(如 n)的方式

  3. 调用方式:静态成员函数可以通过类名直接调用(如 A::getCreationCount()),也可以通过类的对象调用

静态成员函数通常用于提供一些与类的任何特定实例无关的功能,或者访问静态成员变量,而不依赖于类的对象。在设计类时,如果某个函数的行为不需要依赖于对象的状态,那么就应该将其声明为静态的

1.1特性

  1. 静态成员为所有类对象所共享,不属于某个具体的对象,存放在静态区
  2. 静态成员变量必须在类外定义,定义时不添加static关键字,类中只是声明
  3. 类静态成员即可用 类名::静态成员 或者 对象.静态成员 来访问
  4. 静态成员函数没有隐藏的this指针,不能访问任何非静态成员
  5. 静态成员也是类的成员,publicprotectedprivate 访问限定符的限制

2.友元

友元(Friend)在C++中是一个重要的概念,它允许某些特定的外部函数或类访问另一个类的私有(private)或受保护(protected)成员

2.1引入:<<和>>的重载

我们在前面讲到各种运算符重载,以及赋值运算符重载,那么能不能直接重载<<>>,实现输入和输出呢?

class Date
{
public:
Date(int year, int month, int day)
     : _year(year)
     , _month(month)
     , _day(day)
 {}
private:
 int _year;
 int _month;
 int _day;
};

如果我们想用这两个运算符,就得自己实现重载

cout
cout是ostream类的一个对象,运算符重载可以这样写:

void operator<<(ostream& out)
{
	out << _year << "-" << _month << "-" << _day;
}

我们现在调用这个重载发现报错:
在这里插入图片描述
因为这里左操作数是d1,所以调用是这种方式:

class Date
{
public:
	Date(int year, int month, int day)
		: _year(year)
		, _month(month)
		, _day(day)
	{}
	void operator<<(ostream& out)
	{
		out << _year << "-" << _month << "-" << _day;
	}
private:
	int _year;
	int _month;
	int _day;
};

意味着我们必须这样调用

d1<<cout;

不符合常规调用

作为成员函数重载,this指针占据了第一个参数,意味着Date必须是左操作数

所以,这个这个函数只能写为全局函数,而不能是成员函数

但是这里访问不了私有成员,我们先将其置为公有

class Date
{
public:
	Date(int year, int month, int day)
		: _year(year)
		, _month(month)
		, _day(day)
	{}
	
//private:
	int _year;
	int _month;
	int _day;
};
void operator<<(ostream& out,Date &d)
{
	out << d._year << "-" << d._month << "-" <<d._day;
}
int main()
{
	Date d1(2005, 6, 23);
	cout << d1;
	return 0;
}

这下可以进行访问
在这里插入图片描述
但是,这里并不能进行连续的输出,比如以下形式:

Date d1(2005, 6, 23);
Date d2(2024, 4, 2);
cout << d1<<d2;

在这里插入图片描述
我们可以这么理解,cout是从左向右进行的,与连续赋值相反的顺序,cout<<d1,返回cout,返回值作为左操作数,再进行流插入,所以我们得增加ostream的返回值:

ostream& operator<<(ostream& out,const Date &d)
{
	out << d._year << "-" << d._month << "-" <<d._day;
	return out;
}

同理可以完成流提取:

istream& operator>>(istream& _cin, Date& d)
{
 _cin >> d._year;
 _cin >> d._month;
 _cin >> d._day;
 return _cin;
}

2.2友元函数

现在尝试去重载operator<<,然后发现没办法将operator<<重载成成员函数。因为cout的输出流对象和隐含的this指针在抢占第一个参数的位置。this指针默认是第一个参数也就是左操作数了。但是实际使用中cout需要是第一个形参对象,才能正常使用。所以要将operator<<重载成全局函数。但又会导致类外没办法访问成员,此时就需要友元来解决。operator>>同理

友元函数可以直接访问类的私有成员,它是定义在类外部的普通函数,不属于任何类,但需要在类的内部声明,声明时需要加friend关键字

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;
};

特点

  • 友元函数可访问类的私有和保护成员但不是类的成员函数
  • 友元函数不能用const修饰
  • 友元函数可以在类定义的任何地方声明不受类访问限定符限制
  • 一个函数可以是多个类的友元函数
  • 友元函数的调用与普通函数的调用原理相同

2.3友元类

友元类的所有成员函数都可以是另一个类的友元函数,都可以访问另一个类中的非公有成员

class Time
{
	friend class Date;
public:
	Time(int hour = 0, int minute = 0, int second = 0)
		: _hour(hour)
		, _minute(minute)
		, _second(second)
	{}

private:
	int _hour;
	int _minute;
	int _second;
};
class Date
{
public:
	Date(int year = 1900, int month = 1, int day = 1)
		: _year(year)
		, _month(month)
		, _day(day)
	{}

	void SetTimeOfDate(int hour, int minute, int second)
	{
		// 直接访问时间类私有的成员变量
		_t._hour = hour;
		_t._minute = minute;
		_t._second = second;
	}

private:
	int _year;
	int _month;
	int _day;
	Time _t;
};
  • 友元关系是单向的,不具有交换性:比如上述Time类和Date类,在Time类中声明Date类为其友元类,那么可以在Date类中直接访问Time类的私有成员变量,但想在Time类中访问Date类中私有的成员变量则不行

  • 友元关系不能传递:如果C是B的友元, B是A的友元,则不能说明C时A的友元

  • 友元关系不能继承

3.内部类

如果一个类定义在另一个类的内部,这个内部类就叫做内部类。内部类是一个独立的类,它不属于外部类,更不能通过外部类的对象去访问内部类的成员。外部类对内部类没有任何优越的访问权限

内部类就是外部类的友元类,参见友元类的定义,内部类可以通过外部类的对象参数来访问外部类中的所有成员。但是外部类不是内部类的友元

class A
{
private:
	static int k;
	int h;
public:
	class B // B天生就是A的友元
	{
	public:
		void fun(const A& a)
		{
			cout << k << endl;//OK
			cout << a.h << endl;//OK
		}
	};
};
int A::k = 1;
int main()
{
	A::B b;
	b.fun(A());

	return 0;
}

B可以访问A的所有成员

特性

  1. 内部类可以定义在外部类的publicprotectedprivate都是可以的。
  2. 注意内部类可以直接访问外部类中的static成员,不需要外部类的对象/类名
  3. sizeof(外部类)=外部类,和内部类没有任何关系
A::B b;

B这个类受到A类的类域的限制

4.匿名对象

class A
{
public:
	A(int a = 0)
		:_a(a)
	{
		cout << "A(int a)" << endl;
	}
	~A()
	{
		cout << "~A()" << endl;
	}
private:
	int _a;
};

有名对象:

A aa1;
A aa2(100);

匿名对象:

A();
A(11);

在这个代码示例中:

class A
{
public:
    A(int a = 0)
        :_a(a)
    {
        cout << "A(int a)" << endl;
    }
    ~A()
    {
        cout << "~A()" << endl;
    }
private:
    int _a;
};

int main()
{
    A();
    return 0;
}

A(); 这一行创建了一个 A 类的匿名对象。匿名对象是指在创建时没有被赋予一个变量名的对象。它们通常用于临时的操作,比如传递对象作为函数参数,或者从函数返回对象时不需要保留对象的名称

在这个特定的例子中,A(); 创建了一个 A类型的实例,但没有变量名与之关联。这意味着这个对象只在它被创建的那条语句中存在。一旦这条语句执行完毕,这个匿名对象的生命周期就结束了,它会被立即销毁。因此,紧接着构造函数的调用后,析构函数也会被立即调用

输出将会是:

A(int a)
~A()
class Solution {
public:
 int Sum_Solution(int n) {
 //...
 return n;
 }
};
int main()
{
	Solution().Sum_Solution(10);
	return 0;
}

匿名对象在这样场景下就很好用,当我需要一个临时对象去调用其成员函数,但又不想为这个临时使用的对象创建一个具体的变量名,这样使用就很方便

5.拷贝对象时的一些编译器优化

在传参和传返回值的过程中,一般编译器会做一些优化,减少对象的拷贝

class A
{
public:
	A(int a = 0)
		:_a(a)
	{
		cout << "A(int a)" << endl;
	}
	A(const A& aa)
		:_a(aa._a)
	{
		cout << "A(const A& aa)" << endl;
	}
	A& operator=(const A& aa)
	{
		cout << "A& operator=(const A& aa)" << endl;
		if (this != &aa)
		{
			_a = aa._a;
		}
		return *this;
	}
	~A()
	{
		cout << "~A()" << endl;
	}
private:
	int _a;
};
void f1(A aa)
{}
A f2()
{
	A aa;
	return aa;
}

我们在主函数设置几个变量来测试一下优化行为:

A aa1=2;

这里原本应该是构造加拷贝构造,在优化下合为构造:
在这里插入图片描述

int main()
{
	const A& aa2 = 2;
	return 0;
}

这里就只有构造,没有拷贝构造,没有进行优化,原因如下:

当你在C++中使用const A& aa2 = 2;这行代码时,其实发生了一系列复杂的操作,它们体现了C++对于效率和对象生命周期管理的考量。下面是详细解释:

  1. 临时对象的构造
    首先,2是一个整数字面量,它本身并不是A类型的对象。当这行代码执行时,C++需要一个A类型的对象来与aa2绑定。因此,编译器查找A类,找到了一个可以接受单个整数作为参数的构造函数A(int a = 0)。使用这个构造函数,编译器创建一个匿名的A类型的临时对象。这个过程中,构造函数被调用,打印出A(int a)

  2. 绑定到常量引用
    在很多编程语言中,通常不能直接将一个临时对象(或字面量)赋值给一个引用。然而,C++允许一个临时对象绑定到一个常量引用上。这里的const A& aa2 = 2;正是这样的情况。这行代码实际上告诉编译器:“创建一个临时的A对象,并将aa2作为这个临时对象的一个常量引用。” 因此,没有拷贝构造函数被调用,因为我们没有创建一个新的A对象,只是创建了一个临时对象的引用

  3. 没有拷贝构造的调用
    在这个过程中,临时对象是直接在需要的位置构造的,然后aa2被绑定到这个对象上。由于aa2是一个引用,它实际上并不拥有对象;它只是一个到临时对象的链接。因此,不需要调用拷贝构造函数来创建一个新的A对象,这个机制避免了不必要的拷贝,提高了效率

  4. 常量引用延长临时对象的生命周期
    在C++中,将临时对象绑定到常量引用上的一个重要后果是,这个临时对象的生命周期会被延长,以匹配引用的生命周期。这意味着,尽管我们用一个整数字面量初始化了aa2,这个匿名的A对象将会持续存在直到aa2离开作用域。如果没有这个特性,临时对象将在表达式结束时立即被销毁,这将导致引用悬挂,引用一个已经不存在的对象

void f1(A aa)
{}
int main()
{
	f1(A(2));
	return 0;
}

这里传了一个匿名对象,先调用构造函数,再调用拷贝构造函数
在这里插入图片描述
编译器优化,优化为构造函数

A f2()
{
	A aa;
	return aa;
}
int main()
{
	f2();
	return 0;
}

f2应该有一个构造和一个拷贝构造
在这里插入图片描述
优化为构造函数

A f2()
{
	A aa;
	return aa;
}
int main()
{
	A aa2 = f2();
	return 0;
}

如果不优化,f2()会有一个构造和一个拷贝构造,最后,这个临时副本将会被用来初始化main函数中的A aa2,再次调用拷贝构造函数,则这里是连续的拷贝构造函数
在这里插入图片描述
编译器优化为构造函数

A f2()
{
	A aa;
	return aa;
}
int main()
{
	A aa1;
	aa1 = f2();
	return 0;
}

在这里插入图片描述
一个表达式中,连续拷贝构造+赋值重载,结果没有发生很大的优化

本节内容到此结束!感谢大家阅读!!!!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/511419.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Tuxera NTFS for Mac2023绿色免费版 免费的ntfs for mac 免费读写硬盘U盘工具

Tuxera NTFS 2023 Mac免费版是款适合Mac用户使用的磁盘读写工具。Tuxera NTFS 2023 Mac可以很好的帮助用户在Mac上打开、编辑、复制、移动或删除存储在Windows NTFS格式的USB驱动器上的文件。并且Tuxera NTFS 2023 Mac还可以无阻碍地使用各种文件系统磁盘&#xff0c;还能解决磁…

【HTML】制作一个简单的动态SVG图形

目录 前言 开始 HTML部分 CSS部分 效果图 总结 前言 无需多言&#xff0c;本文将详细介绍一段HTML和CSS代码&#xff0c;该代码用于创建一个动态的SVG图形&#xff0c;具体内容如下&#xff1a; 开始 首先新建文件夹&#xff0c;创建两个文本文档&#xff0c;其中HTML的文…

基于 NGINX 的 ngx_http_geoip2 模块 来禁止国外 IP 访问网站

基于 NGINX 的 ngx_http_geoip2 模块 来禁止国外 IP 访问网站 一、安装 geoip2 扩展依赖 [rootfxkj ~]# yum install libmaxminddb-devel -y二、下载 ngx_http_geoip2_module 模块 [rootfxkj tmp]# git clone https://github.com/leev/ngx_http_geoip2_module.git三、解压模…

mysql忘记密码

起因是忘记了mysql的密码&#xff0c;cmd那里显示mysql不是内部命令&#xff0c;于是先把mysql加入到环境变量当中&#xff1a;设置-高级系统设置-环境变量-系统变量那里编辑&#xff0c;将mysql的路径加入到path当中。 然后遇到报错&#xff1a;ERROR 2003 (HY000): Cant conn…

Adobe Bridge 2024:连接创意,探索无限可能 mac/win版

Adobe Bridge 2024&#xff0c;作为Adobe家族中的一款强大的创意管理工具&#xff0c;再次革新了数字资产管理和工作流程优化的标准。这款软件不仅继承了Adobe Bridge一贯的直观界面和强大功能&#xff0c;更在多个方面进行了突破性的改进。 Bridge 2024软件获取 全面的资源管…

iOS开发进阶(十四):xcodebuild 命令应用详解

文章目录 一、前言二、xcodebuild 命令汇总三、xcodebuild 可选命令四、exportOptionsPlist文件内容配置说明五、项目实操六、拓展阅读 一、前言 关于iOS组包&#xff0c;详参博文《ReactNative进阶&#xff08;三十四&#xff09;&#xff1a;Jenkins 流水线 组包 iOS 应用包…

如何提bug?

很多公司都有提bug的标准&#xff0c;对于新人刚介入测试行业时&#xff0c;提bug的时候&#xff0c;描述的清晰与否就很重要&#xff0c;那一个很明朗清晰的bug应该包含那些呢&#xff1f; bug包含的要素有那些&#xff1f;&#xff08;以jira工具为例&#xff09; 1、项目名…

LeetCode每日一题之专题一:双指针 ——移动零

移动零OJ链接&#xff1a;283. 移动零 - 力扣&#xff08;LeetCode&#xff09; 题目&#xff1a; 解法&#xff08;快排的思想&#xff1a;数组划分区间-数组分两块&#xff09;&#xff1a; 算法思路&#xff1a;在本题中&#xff0c;我们可以用一个 dest 指针来扫描整个数组…

应用方案 | 内置ALC的音频前置放大器D2538A和D3308芯片

一、应用领域 D2538A和D3308是芯谷科技推出的两款内置ALC&#xff08;音频限幅器&#xff09;的前置音频放大器芯片&#xff0c;其中D2538A为单通道&#xff0c;D3308为双通道&#xff0c;它特别适用于胎心仪、个人医疗防护、立体声收录机、盒式录音机等涉及音频放大与限幅的产…

Java游戏开发基础:从零开始搭建自己的游戏之《人生重开模拟器》简易版

一、引言 人生重开模拟器游戏是一种虚拟角色扮演游戏&#xff0c;玩家通过控制一个虚构的角色&#xff0c;体验与现实生活中不同的选择和结果。玩家的决策将影响角色的生活轨迹&#xff0c;包括他们的职业生涯、社交关系、健康和财富等方面。 游戏的乐趣在于提供了一个虚拟的沙…

1.JavaEE进阶篇 - 为什么要学习SpringBoot呢?

文章目录 1.为什么要学框架&#xff1f;2.框架的优点展示(SpringBoot VS Servlet)2.1 Servlet 项⽬开发2.1.1 创建项⽬2.1.2 添加引⽤2.1.3 添加业务代码2.1.4 运⾏项⽬(配置tomcat)2.1.5 Maven配置2.1.5.1修改本地Maven仓库地址2.1.5.2 配置settings.xml文件2.1.5.3项目 本地仓…

LeetCode 19.删除链表的倒数第N个结点

给你一个链表&#xff0c;删除链表的倒数第 n 个结点&#xff0c;并且返回链表的头结点。 示例 1&#xff1a; 输入&#xff1a;head [1,2,3,4,5], n 2 输出&#xff1a;[1,2,3,5] 示例 2&#xff1a; 输入&#xff1a;head [1], n 1 输出&#xff1a;[] 示例 3&#x…

大数据实验一,Hadoop安装及使用

目录 一&#xff0e;实验内容 二&#xff0e;实验目的 三&#xff0e;实验过程截图及说明 1、安装SSH&#xff0c;并配置SSH无密码登录 2、配置java环境 3.Hadoop的安装与配置 4、修改四个配置文件&#xff1a; 5、格式化HDFS的NameNode&#xff1a; 6、启动Hadoop 7、…

Polardb MySQL 产品架构及特性

一、产品概述; 1、产品族 参考&#xff1a;https://edu.aliyun.com/course/3121700/lesson/341900000?spma2cwt.28120015.3121700.6.166d71c1wwp2px 2、polardb mysql架构优势 1&#xff09;大容量高弹性&#xff1a;最大支持存储100T&#xff0c;最高超1000核CPU&#xff0…

PEFT-LISA

LISA是LoRA的简化版&#xff0c;但其抓住了LoRA微调的核心&#xff0c;即LoRA侧重更新LLM的底层embedding和顶层head。 根据上述现象&#xff0c;LISA提出两点改进&#xff1a; 始终更新LLM的底层embedding和顶层head随机更新中间层的hidden state 实验结果 显存占用 毕竟模型…

游戏引擎之高级动画技术

一、动画混合 当我们拥有各类动画素材&#xff08;clips&#xff09;时&#xff0c;要将它们融合起来成为一套完整的动画。 最经典的例子就是从走的动画自然的过渡到跑的动画。 1.1 线性插值 不同于上节课的LERP&#xff08;同一个clip内不同pose之间&#xff09;&#xff…

STM32学习和实践笔记(4):分析和理解GPIO_InitTypeDef GPIO_InitStructure (c)

第二个成员变量是GPIOSpeed_TypeDef GPIO_Speed&#xff1b;也与int a一样同理。 GPIOSpeed_TypeDef是一个枚举类型&#xff0c;其定义如下&#xff1a; typedef enum { GPIO_Speed_10MHz 1, GPIO_Speed_2MHz, GPIO_Speed_50MHz }GPIOSpeed_TypeDef; #define IS_GPI…

STM32-03基于HAL库(CubeMX+MDK+Proteus)输入检测案例(按键控制LED)

文章目录 一、功能需求分析二、Proteus绘制电路原理图三、STMCubeMX 配置引脚及模式&#xff0c;生成代码四、MDK打开生成项目&#xff0c;编写HAL库的按键检测代码五、运行仿真程序&#xff0c;调试代码 一、功能需求分析 搭建完成开发STM32开发环境之后&#xff0c;开始GPIO…

vue结合Elempent-Plus/UI穿梭框更改宽度以及悬浮文本显示

由于分辨率不同会导致文本内容显示不全&#xff0c;如下所示&#xff1a; 因此需要 1、悬浮到对应行上出现悬浮信息 实现代码如下所示&#xff1a; 这里只演示Vue3版本代码&#xff0c;Vue2版本不再演示 区别就在插槽使用上Vue3使用&#xff1a;#default“”&#xff1b;Vu…

R统计实战:详解机器学习Adaboost的操作步骤与应用

一、引言 机器学习是人工智能的核心领域之一&#xff0c;其重要性体现在其能够从数据中自动学习并改进的能力上。在实际问题中&#xff0c;机器学习已经被广泛应用于各个领域&#xff0c;包括但不限于金融、医疗、电子商务、社交网络等。例如&#xff0c;在金融领域&#xff0c…