【类和对象(下)】

news2024/11/27 10:25:50

文章目录

  • 🍕前言
  • 一、🍕再谈构造函数
    • 1.1构造函数体赋值
    • 1.2初始化列表
    • 1.3explicit关键字
  • 二、🍕static成员
  • 三、🍕友元
  • 四、🍕内部类
  • 五、🍕匿名对象
  • 六、🍕拷贝对象时编译器的一些优化
  • 七、🍕再谈类和对象
  • 🍕总结


🍕前言

本文章继自类和对象(中),完成收尾工作。


一、🍕再谈构造函数

1.1构造函数体赋值

在学习过的类和对象的基础知识中,构造函数内部通常是给成员变量一个初始值。虽然调用完构造函数后,变量有了初始值,但是不能称其为对对象的初始化,只能称其为对变量的赋值。

因为初始化只能初始化一次,而赋值可以多次赋值。

所以下面给出一个真正的初始化操作:初始化列表。

1.2初始化列表

初始化列表是构造函数的一部分,与构造函数体赋值并没有冲突,可以共存,只是初始化列表的每个变量都只能出现一次(因为初始化只初始化一次)。

初始化列表格式:

以一个冒号开始,接着是一个以逗号分隔的数据成员列表,每个"成员变量"后面跟一个放在括号中的初始值或表达式。

比如:

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

构造函数的大括号内部仍然可以像构造函数一样写其他的东西。

注意:

  1. 每个成员变量在初始化列表中只能出现一次(初始化只能初始化一次)
  2. 类中包含以下成员,必须放在初始化列表位置进行初始化:
    (1)引用成员变量
    (2)const成员变量
    (3)自定义类型成员(且该类没有默认构造函数时)

以下面的代码为例:

class B
{
public:
	class A
	{
	public:
		A(int a = 1)
			:_a(a)
		{}
	private:
		int _a;
	};

	B(int a, int ref,int n)
		: _ref(ref)
		, _n(10)
	{}
private:
	A _aobj; //没有默认构造函数
	int& _ref;   // 引用
	const int _n; // const
};

不同于其他内置类型,其他内置类型可以只定义不初始化,比如:

int a;
double b;
char c;

对于引用成员变量和const成员变量来说,它们有一个共同点:
在定义的时候必须初始化。

所以在类的构造函数的初始化列表中必须有这两个成员变量的存在。

而对于自定义类型,前面我们说过,编译器自己生成的默认构造函数对内置类型不做处理,对自定义类型调用它的默认构造函数。

在初始化列表中,如果该自定义类型没有默认构造函数,(默认构造函数包括:无参构造函数,全缺省构造函数和编译器自己生成的构造函数。其中如果我们不写构造函数,编译器才会自己生成)那么在初始化列表必须要对自定义类型初始化。

如果不初始化,会报错,比如:
在这里插入图片描述
在这个例子中,A类的构造函数并不是默认构造函数,而是一个普通的构造函数,并且在B类的构造函数的初始化列表中并没有对A类进行初始化,这就会产生报错。

总结:

  1. 每个成员变量在初始化列表中只能出现一次(初始化只能初始化一次)

  2. 类中包含以下成员,必须放在初始化列表位置进行初始化:
    (1)引用成员变量
    (2)const成员变量
    (3)自定义类型成员(且该类没有默认构造函数时)

  3. 尽量使用初始化列表初始化,因为不管你是否使用初始化列表,对于自定义类型成员变量,一定会先使用初始化列表初始化。

  4. 成员变量在类中声明次序就是其在初始化列表中的初始化顺序,与其在初始化列表中的先后次序无关

第四点是非常重要的一点,下面给一道题:

class A
{
public:
	A(int a)
	  :_a1(a)
	  ,_a2(_a1)
	{}
 
	void Print() 
	{
	    cout<<_a1<<" "<<_a2<<endl;
	}

private:
	 int _a2;
	 int _a1;
};

int main() 
{
	 A aa(1);
	 aa.Print();
}

A.输出1  1
B.程序崩溃
C.编译不通过
D.输出1  随机值

这道题选择D项。首先排除程序会崩溃。
程序崩溃的原因不多,主要就是动态申请的资源泄露了或者产生死循环等等。

明显本题并没有以上情况。这道题既然能放在这里,说明该题可能会有坑,可以排除A,那就从CD之间选择。认真看代码,发现没有什么语法错误,不会编译不通过,只能选一个看似最离谱的答案D。
解析:类A实例化一个对象aa并显式地传递一个1给形参a,调用它的构造函数,此时在初始化列表中看似先初始化a1,再初始化a2,实际上是先初始化a2,再初始化a1,因为初始化的顺序取决于成员变量的声明顺序的!

所以:成员变量在类中声明次序就是其在初始化列表中的初始化顺序,与其在初始化列表中的先后次序无关

1.3explicit关键字

explicit关键字的作用在于禁止将会禁止构造函数的隐式转换。
对于什么是隐式转换,请看下面的代码:

class Date
{
public:
	explicit Date(int year)
		:_year(year)
	{}
	//赋值运算符重载
	Date& operator=(const Date& d)
	{
		if (this != &d)
		{
			_year = d._year;
			_month = d._month;
			_day = d._day;
		}
		return *this;
	}
private:
	int _year;
	int _month;
	int _day;
};
int main()
{
	Date d2 = 2;
}

实例化的类对象中,看似是将变量2赋值给对象d2,实际上的情况是:对于不同类型的赋值,会在中间生成一个临时空间,整型2发生了隐式类型转换,升级成了类对象,这个类对象2调用构造函数,此时这块临时空间就是一个对象,然后该临时空间再调用拷贝构造函数拷贝给d2。

这个过程是:构造–>拷贝构造。

但是编译器会自动优化,优化成直接进行构造。

对于编译器的优化,我们会在下面的篇幅详细地讲到。

对于explicit关键字,就是禁止编译器对于这种隐式类型转化。

当我们加上exiplict关键字后,编译器会直接报错。在这里插入图片描述


二、🍕static成员

被static修饰的成员变量叫做静态成员变量,被修饰的成员函数叫做静态成员函数。普通成员变量属于对象自己所有,静态成员变量属于每个类对象所共有的。

注意:1.静态成员变量一定要在类外面进行初始化。
2.静态成员变量或静态成员函数可以直接通过类域访问。(因为它属于整个类)

对于静态成员变量, 如果设置成公有,那就跟全局变量几乎一样,都可以被类外面进行访问。

下面有一道面试题:实现一个类,计算程序中出现了多少类对象。

为了完成这道题目,我们实现的类不能在外面计算出现的类的次数。只能在类内计算,如果调用了构造函数或者拷贝构造函数,就让一个计数变量++,如果调用了析构函数,就让计数变量–。很明显需要用静态成员函数才能实现,为了获得该成员变量,我们需要在对象内实现一个静态成员函数,来跟静态成员变量进行配套使用。

静态成员函数的特点:
1.没有this指针
2.指定类域和访问限定符即可访问。

所以可以通过下面的代码来计算类的对象的个数。

class A
{
public:
	A() 
	{
		++_scount; 
	}
	A(const A& t) 
	{ 
		++_scount; 
	}
	~A() 
	{ 
		--_scount;
	}
	static int GetACount() 
	{
		return _scount;
	}
private:
	static int _scount;
};

另一道题目:
设计一个类,在类外面只能在栈/堆上创建一个对象。

class A
{
public:
	static A GetStackObj()
	{
		A a;
		return a;
	}

	static A* GetHeapObj()
	{
		return new A;
	}

private:
	A()
	{}
private:
	int _a1;
	int _a2;
};

int main()
{
	A::GetStackObj();
	A::GetHeapObj();

	return 0;
}

解析:将构造函数设置成私有的,那么所有的类型的对象都不能在类外面实例化
但是又将栈/堆的成员函数设置成静态公有的,使得我们可以在类外面直接通过类作用限定符
//直接调用该静态成员函数,达到题目要求。

再补充几点:
1.不能通过在类中给静态成员变量缺省值,因为这个地方只是静态成员变量的声明,并且缺省值实际上是给初始化列表的,对于普通成员变量来说,可以在构造函数的初始化列表中进行初始化,而静态成员变量则无法实现。
2.可以通过普通成员函数访问静态成员函数,因为对于静态成员函数来说,只要给定类域和访问限定符,就可以访问静态成员函数,在类中是不受访问限定符的限制的,也不受类域的限制,因为对于类来说这是一个整体;但不能通过静态成员函数访问普通成员函数,因为静态成员函数没有this指针,无法访问普通成员函数和成员变量。


三、🍕友元

友元的出现突破了类的封装机制,友元就像是类的朋友,可以随意访问类的非公有成员变量和成员函数,所以友元不适合多用。

友元分为友元函数和友元类。
友元函数:
我们知道,一般内置类型的输出可以使用

cout << 内置类型

的方式进行输出。但是对于一个自定义类型,却不能定义成类的成员函数:operator<<。因为在成员函数中有一个隐含的this指针,该指针会一直占用第一个位置,导致左操作数只能是cout。
所以如果要定义成成员函数,只能实现成 d1 << cout。(假设d1是一个日期类对象)。

如果定义成全局函数,就不再有this指针,左操作数就可以正常地中作为cout。但是重载成全局函数又导致我们无法访问到类地成员变量,这就需要友元函数的出现。

友元函数关键字: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;
};

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

istream& operator>>(istream& _cin, Date& d)
{
	_cin >> d._year;
	_cin >> d._month;
	_cin >> d._day;
	return _cin;
}
int main()
{
	Date d;
	cin >> d;
	cout << d << endl;
	return 0;
}

注意:

1.友元函数可以直接访问类的私有成员,它是定义在类外部的普通函数,不属于任何类,但需要在类的内部声明,声明时需要加friend关键字。
2.友元函数可访问类的私有和保护成员,但不是类的成员函数
3.友元函数不能用const修饰
4.友元函数可以在类定义的任何地方声明,不受类访问限定符限制
5.一个函数可以是多个类的友元函数
6.友元函数的调用与普通函数的调用原理相同


四、🍕内部类

如果一个类定义在外部类的内部,那么这个类就叫做内部类。内部类不属于外部类,外部类不能通过任何途径访问内部类的成员。

注意:内部类天生就是外部类的友元,但外部类不是内部类的友元。

特性1:
sizeof(外部类) == 外部类,计算一个类的大小,与内部类完全没有任何关系,结果是外部类的大小。
比如:

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

int A::k = 1;

int main()
{
	A a;
	cout << sizeof(a) << endl;
	return 0;
}

结果是4;

解析:对于外部类来说,内部类和外部类没有声明关系,因为内部类只是一个声明,并没有真正意义地创建内部类对象。相当于A类是一个图纸,通过A类这个图纸建造了a这所房子,但是B是A类的图纸里面的一张图纸,并没有通过B这个图纸真正创造一所房子。

特性2:
内部类受外部类的访问限定符的限制。

特性3:
建议内部类的成员变量声明在外部类,这样内部类既可以使用它的成员变量,外部类也可以使用内部类的成员变量。
例题:求和


class Solution {
public:
    int Sum_Solution(int n) 
    {
        Sum a[n];
        return _sum;       
    }

    class Sum
    {
    public:
        Sum()
        {
            _sum+=_i;
            ++_i;
        }
    };
    private:
        static int _i;
        static int _sum;
};

int Solution::_i = 1;
int Solution::_sum = 0;


五、🍕匿名对象

请看下面的代码以及注释:

class A
{
public:
	A(int a = 0)
	:_a(a)
	{
	cout << "A(int a)" << 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 aa2(2);

//不能这样调用:
	//Solution.Sum_Solution(10);
	//Solution::.Sum_Solution(10);
	//对于第1种情况,不能直接使用类型调用它的成员函数
	//对于第二种情况,只有static成员函数或者成员变量可以这样调用,因为静态成员函数没有this指针,而普通成员函数需要传递this指针,如果这样调用,就无法传递this指针。
	Solution().Sum_Solution(10);
	return 0;
}

匿名对象特性:

1.匿名对象的生命周期在当前行
有名对象的生命周期在当前的局部作用域
2.匿名对象类似于临时对象,都具有常性
3const引用延长了匿名对象的生命周期

1.匿名对象的生命周期只有1A(2);
2.匿名对象具有常性,如果这样定义结果是权限放大了,不行
	A& pa = A(3);
	const A& ra = A(3);//这样就可以,const修饰的变量具有常性,这样是权限平移。
3.const引用修饰的匿名对象延长了它的生命周期,使其生命周期延长到当前函数作用域。

六、🍕拷贝对象时编译器的一些优化

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

几种优化情况:
1.同一行一个表达式中连续的构造+构造会优化成为1个构造。
2.同一行一个表达式中连续的构造+拷贝构造会优化成为1个构造
3.不同行的构造+赋值重载不会优化

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

int main()
{
	// 隐式类型,连续构造+拷贝构造->优化为直接构造
	f1(1);
	// 一个表达式中,连续构造+拷贝构造->优化为一个构造
	f1(A(2));

	// 一个表达式中,连续拷贝构造+拷贝构造->优化一个拷贝构造
	A aa2 = f2();

	// 一个表达式中,连续拷贝构造+赋值重载->无法优化
	A aa2;
	aa2 = f2();

	return 0;
}

七、🍕再谈类和对象

类是对某一类实体(对象)来进行描述的,描述该对象具有那
些属性,那些方法,描述完成后就形成了一种新的自定义类型,才用该自定义类型就可以实例化具体的对象。


🍕总结

类和对象收尾工作到此结束。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

香港Web3,已走至黎明前夜 能否抓住机遇,是外界始终关注的话题?

香港对Web3的全面开放&#xff0c;既是挑战&#xff0c;又是一个不容错过的发展机遇。香港相关行业是否有足够的基础和能力抓住金融大变局的历史性契机&#xff0c;面向未来&#xff0c;完善金融生态&#xff0c;提升香港金融中心的国际竞争力&#xff0c;是外界始终关注的话题…

mongodb geohash

地理位置索引支持是MongoDB的一大亮点&#xff0c;这也是全球最流行的LBS服务foursquare 选择MongoDB的原因之一。我们知道&#xff0c;通常的数据库索引结构是B Tree&#xff0c;如何将地理位置转化为可建立BTree的形式&#xff0c;下文将为你描述。 首先假设我们将需要索引的…

Apache Doris简单易用、高性能和统一的分析数据库

Doris 介绍 https://github.com/apache/doris Apache Doris 是一个基于 MPP 架构的高性能、实时的分析型数据库&#xff0c;以极速易用的特点被人们所熟知&#xff0c;仅需亚秒级响应时间即可返回海量数据下的查询结果&#xff0c;不仅可以支持高并发的点查询场景&#xff0c;…

认识BACnet协议

一、什么是BACnet&#xff1f; BACnet&#xff0c;Building Automation and Control networks的简称&#xff0c;即楼宇自动化与控制网络。是用于智能建筑的通信协议。 一般楼宇自控设备从功能上讲分为两部分&#xff1a;一部分专门处理设备的控制功能&#xff1b;另一部分专…

设计模式之【代理模式】,有事找我“经纪人”

文章目录 一、什么是代理模式1、代理模式三大角色2、代理、桥接、装饰器、适配器 4 种设计模式的区别3、代理模式使用场景4、代理模式优缺点 二、静态代理1、静态代理的一般写法2、火车站售票案例3、静态代理优缺点 三、动态代理1、静态代理和动态代理的本质区别2、JDK动态代理…

ArcSWAT报错:Error Number :-2147467259; 对 COM 组件的调用返回了错误 HRESULT E_FAIL

文章目录 1 报错内容2 报错解决3 并行处理的设置补充说明 1 报错内容 通常为连续两段报错&#xff1a; Error Number :-2147467259 Error Message :对 COM 组件的调用返回了错误 HRESULT E_FAIL 。 Module name : mSWFlow Function name : createStream Procedure ( error li…

星辰天合参加首届数字驱动创新峰会 强调以 SDS 加速数据基础设施建设

5 月 11 日&#xff0c;2023 数字驱动创新峰会在北京新世纪日航饭店隆重举办。作为赛迪网、《数字经济》杂志社首次主办的数字驱动峰会&#xff0c;本届峰会以“新要素、新生产、新经济”为主题&#xff0c;下设数字金融创新论坛、数字制造创新论坛和数字服务创新论坛三个分论坛…

4 月 NFT 月报:在动荡的 NFT 市场中寻求生存

作者&#xff1a;lesleyfootprint.network 数据来源&#xff1a;Footprint NFT Research 上个月&#xff0c;NFT市场在 4 月 5 日出现了交易量高峰&#xff0c;随后交易量又在月底大幅下降了 50%。近期&#xff0c;NFT 卖家的数量持续超过买家的数量&#xff0c;这表明市场可…

4面华为测试开发,居然挂在这个地方....

说一下我面试别人时候的思路 反过来理解&#xff0c;就是面试时候应该注意哪些东西&#xff1b;用加粗部分标注了 一般面试分为这么几个部分&#xff1a; 一、自我介绍 这部分一般人喜欢讲很多&#xff0c;其实没必要。大约5分钟内说清楚自己的职业经历&#xff0c;自己的核…

基于Docker的深度学习环境NVIDIA和CUDA部署以及WSL和linux镜像问题

基于Docker的深度学习环境部署 1. 什么是Docker&#xff1f;2. 深度学习环境的基本要求3. Docker的基本操作3.1 在Windows上安装Docker3.2 在Ubuntu上安装Docker3.3 拉取一个pytorch的镜像3.4 部署自己的项目3.5 导出配置好项目的新镜像 4. 分享新镜像4.1 将镜像导出为tar分享给…

安卓源码下apk进行platform签名的方法

目录 一 任意目录下创建一个文件夹 二 该目录下需要准备的5个文件 三 执行命令 四 生成结果 一 任意目录下创建一个文件夹 二 该目录下需要准备的5个文件 上述五个文件&#xff0c; 前四个可以从编译好的安卓源码工程目录下复制&#xff0c; 第五个是自己需要签名的apk文件 …

抖音谋局本地生活“大蛋糕”|成都待慕电商

打开抖音APP&#xff0c;“同城”里囊括的美食、休闲娱乐、丽人美发、酒店民宿、周边旅游等让消费者们眼花缭乱&#xff0c;似乎正在打造另一个短视频版本的同城服务商。 4月25日&#xff0c;2023抖音生活服务生态伙伴大会在成都举行。《每日经济新闻》记者看到&#xff0c;活…

基于WiFi的CSI数据做呼吸频率检测-python版(含代码和数据)

一、概述 本Demo无需机器学习模型&#xff0c;Demo功能涉及的理论主要参考了硕士学位论文《基于WiFi的人体行为感知技术研究》&#xff0c;作者是南京邮电大学的朱XX&#xff0c;本人用python复现了论文中呼吸频率检测的功能。Demo实现呼吸速率检测的主要过程为&#xff1a; …

Java面试知识点(全)-设计模式三

Java面试知识点(全) 导航&#xff1a; https://nanxiang.blog.csdn.net/article/details/130640392 注&#xff1a;随时更新 18、责任链模式&#xff08;Chain of Responsibility&#xff09; 接下来我们将要谈谈责任链模式&#xff0c;有多个对象&#xff0c;每个对象持有对…

基于stm32mp157 linux开发板ARM裸机开发教程6:ARM 汇编语言程序设计(连载中)

前言&#xff1a; 目前针对ARM Cortex-A7裸机开发文档及视频进行了二次升级持续更新中&#xff0c;使其内容更加丰富&#xff0c;讲解更加细致&#xff0c;全文所使用的开发平台均为华清远见FS-MP1A开发板&#xff08;STM32MP157开发板&#xff09; 针对对FS-MP1A开发板&…

Scrapy 框架介绍

一、Scrapy是什么 Scrapy 是一个基于 Twisted 的异步处理框架&#xff0c;是纯 Python 实现的爬虫框架&#xff0c;其架构清晰&#xff0c;模块之间的耦合程度低&#xff0c;可扩展性极强&#xff0c;可以灵活完成各种需求。我们只需要定制开发几个模块就可以轻松实现一个爬虫。…

索引有哪些优缺点?索引有哪几种类型?

目录 一、什么是索引&#xff1f; 二、索引的优点 三、索引的缺点 四、索引有哪几种数据类型&#xff1f; 一、什么是索引&#xff1f; 索引是一种能够帮组Mysql高效的从磁盘上检索数据的一种数据结构。在MySQL中的InnoDB引擎中&#xff0c;采取了B树的结构来实现索引和数据…

matlabR2021b启动很慢和初始化时间很长解决

工具&#xff1a;MatlabR2021b。 问题记录&#xff0c;在网上下载安装包后&#xff0c;安装后&#xff0c;发现软件启动时间很长。进入界面后软件需要较长时间的初始化。才能就绪。 查询原因为软件需要在启动是查询licence。 首先在安装文件夹中启动Activate MATLAB R2021b。…

python画直线的方法

python画直线的方法&#xff0c;下面介绍三种&#xff1a; 1、使用列表解析法&#xff0c;只需要添加一个数据类型的变量&#xff0c;然后在上面添加一系列的直线&#xff0c;即可得到一条直线。 5、使用循环解析法和 for循环解析法两种方法相结合来画直线&#xff0c;即可得到…

软件测试之jmeter性能测试让你打开一个全新的世界

一、Jmeter简介 1 概述 jmeter是一个软件&#xff0c;使负载测试或业绩为导向的业务&#xff08;功能&#xff09;测试不同的协议或技术。 它是 Apache 软件基金会的Stefano Mazzocchi JMeter 最初开发的。 它主要对 Apache JServ&#xff08;现在称为如 Apache Tomcat…