C++类和对象补充

news2024/12/27 16:37:05

目录

前言:

1. 构造函数->初始化列表

1.1 初始化列表出现原因

1.2 初始化列表写法

2. explicit关键字

2.1 explict的出现

2.2 explict的写法

3. static成员

4. 友元

4.1 友元函数

4.2 友元类 

5. 内部类和匿名对象

5.1 内部类

5.2 匿名对象


前言:

        本篇讲解的内容是类和对象的补充知识,也就是类和对象当中平时不太注意,但是有比较重要的知识。其中包括构造函数中的初始化列表、static成员、友元、内部类、匿名对象,以及部分小知识。如果小伙伴们有需要请向下查看咯。

1. 构造函数->初始化列表

1.1 初始化列表出现原因

        朋友们,还记得我上篇讲的,当我们在实例化一个对象的时候,编辑器会干嘛?会调用类里的构造函数,对类里面的成员变量作合适的初始化。如下:

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

        但是朋友们,有没有想过一个问题?那就是虽然我们在实例化的时候传入了一个参数,但是那真的算是初始化吗?可能有的小伙伴没有理解我的话。那我换一个说法,我们传入一个值用于初始化,并且将这个值赋值给了一个变量,那么这一步算是初始化吗?

        答案是否,这一个过程并不能说是初始化,我可以举一个例子:

    int a = 0;        //初始化a为0

    int b;              //创建了一个变量b
    b = 0;             //赋值b为0

         大家可以看出这两者的区别了吗?我相信是能的,那么再回到我们的构造函数,这个时候我们再看它,他是初始化还是赋值操作?相信大家心里有数了吧。

        不过话又说回来,这个初始化和赋值有什么影响吗?这么看起来好像没有区别啊?确实,如果是这种类型的变量赋值和直接初始化没有区别,但是大家可能忘了某些东西:引用、const成员变量。想到了吗?

    int& c;                        //引用不初始化
    const int d;                //  const成员变量不初始化      
    d = 10;                      //const成员变量重新赋值

         这几个写法很明显是错误的,相信大家也能够分辨出来,那么我们将这两个类型写在类里,此时我们的写法还正确吗?肯定是错误的。

        所以,C++祖师爷基于这个点,为我们的构造函数多添加了一个功能——初始化列表。

1.2 初始化列表写法

        先看一下代码是如何写的:

class A
{
public:
	A(int a)			
		:_a(a)			//初始化列表
	{}
private:
	int _a;
};
class B
{
public:
	B(int a, int ref)
		:_aobj(a)		//初始化列表
		, _ref(ref)
		, _n(10)
	{}
private:
	A _aobj; // 没有默认构造函数
	int& _ref; // 引用
	const int _n; // const
};

        大家看看我定义的两个类,有没有发现它里面的构造函数多了个东西?那就是以冒号开始,用逗号分隔每一个变量,每个变量加上括号和初始值。

        用上述的B类构造函数为例子:

:_aobj(a)       
, _ref(ref)
 , _n(10)

         这就是初始化列表写法,当然,如果我们初始化有特殊的需求,还是可以将功能写在初始化列表下方的花括号中,例如条件判断、申请堆上的空间之类的。

        编辑器有一个对于初始化列表的规定,那就是不管我们写不写,它都会自己去运行一遍,不过它却不会对这些变量做任何的处理,只有我们写了,他才会为其赋初值。

        还有一点需要我们注意,请先看下方代码,并计算_a1和_a2的初始值。

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吧?因为我就是其中之一。

        其实初始化列表的初始化顺序不是我们眼睛看到的那样,它的初始化顺序是变量定义的顺序,看下代码:

private:
    int _a2;
    int _a1;

        也就是说,_a2先用_a1的初始值也就是随机值赋值了,然后_a1再通过1赋值,这也就造成了我们之前产生的小bug。

2. explicit关键字

2.1 explict的出现

        在讲解这个关键字的之前,我想问一个问题,大家知道下面的两种实例化对象的方式有什么不同吗?(不是说写法)。

Date d1(2023,2,17);

Date d2 = {2023,2,17};

         这样问估计有些难为人,我就直接讲了,第一种会直接用这些初始值通过构造函数实例化

        但是第二种不同,第二种通过这些初始值,调用构造函数实例化之后,然后通过赋值的方式将其转换给d2。大家看出区别了吗?也就是说第一种实例化只有构造函数参与,但是第二种不仅有构造函数,还有拷贝构造参与。不过对于现在的编辑器来说已经被优化了,二者都只有构造函数参与,如果按照刚出现的那段时间,用第二种在构造一个对象的时候产生两个变量大小的空间,这务必会影响我们程序运行的效率。

        那我问大家,为什么会出现这个过程呢?先看下方的代码:

    int a = 10;
    double b = (double)a;

         问:我们将a强制类型转换成double类型,int a是否还存在。

        答案是当然存在,强制类型转换只会新开辟一个临时空间存储,然后赋值给b,其中a的参与只有提供10这个数的价值。通过等效的理解方式,大家再去看上面的Date d2 = {2023,2,17};是不是瞬间就明白了?只不过该操作是隐式类型转换

        explict的出现就是为了禁止这种强制类型转换的。

2.2 explict的写法

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;
};
void Test()
{
	Date d1(2022);

	d1 = 2023;            //错误

}

        大家可以看到,如果我们不添加explict,d1 = 2023这种写法是正确的,但是大家可以赋值这段代码进入你的编辑器看一看,这个操作是否还正确?

        所以explict的功能是:用explicit修饰构造函数,将会禁止构造函数的隐式转换。

3. static成员

        对于static关键字,想必大家还是很了解的,但是当他出现在类里面还清楚吗?

        我们都知道static描述的变量,这个变量的生命周期会到程序结束才会停止,而不是局限于函数结束就被销毁。这个特性static也延续到类当中,当用static去修饰类里面的变量,那么这个变量的生命周期就被提升,并且它并不单独属于某个实例化的类对象,而是属于整个类。

        大伙看到这估计有些疑问,他既然写在了类里面,又不属于这个类的对象,那么它在哪?答案是它被存在了静态区。也就是计算类的大小也不会将他算入其中。

class Date
{
public:
    static int a;
private:
	int _year;
	int _month;
	int _day;
};

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

         正常来说,这个类的大小应该是16对吧,因为又4个整型变量嘛,但是其中有一个变量是static修饰的,所以它的大小不计入其中。

        因为这个static修饰的变量不属于某一个变量,所以说,没有任何一个对象想能对其进行初始化,只能有一个操作,那就是类外定义。

int Date::a = 0;

        至于为什么要这样做?想一想,要是每一个对象都能够对static初始化,那么static修饰这个变量有什么意义呢?

        并且static不只是可以修饰变量,还可以修饰成员函数,将这个成员函数变为静态成员函数。

以下几个特性需要大家知道:

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

         知道了这些特点,下面两个问题相信大家也能够回答了。

        1. 静态成员函数可以调用非静态成员函数吗?
        2. 非静态成员函数可以调用类的静态成员函数吗?

4. 友元

        友元提供了一种突破封装的方式,有时提供了便利。但是友元会增加耦合度,破坏了封装,所以友元不宜多用。
        友元分为:友元函数和友元类

4.1 友元函数

        一般来说友元用到的场景不会太多,但是有一个东西如果不用友元那么它的操作方式会变得十分奇怪,那就是重定义<<和>>这两个操作符。

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

int main()
{
	Date d1(1, 2, 3);
	d1 << cout;
	//cout << d1;   //错误
	return 0;
}

        d1 << cout; -> d1.operator<<(&d1, cout); 不符合常规调用,因为成员函数第一个参数一定是隐藏的this,所以d1必须放在<<的左侧。

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

        这个时候友元函数的出现就显得十分重要了:

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

        可以看到我们的友元函数的写法就是将函数定义在类外面,然后在类里面写下声明并在前面添加上friend关键字。声明的位置可以是任意位置,但是我们习惯将其写在类最上方,而且友元函数突破了public、protected、private的限制。也就是友元函数能够直接访问类里面的私有成员。

friend ostream& operator<<(ostream& _cout, const Date& d);
friend istream& operator>>(istream& _cin, Date& d);

 特性:

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

4.2 友元类 

特性:

1. 友元类的所有成员函数都可以是另一个类的友元函数,都可以访问另一个类中的非公有成员。
2. 友元关系是单向的,不具有交换性。比如上述Time类和Date类,在Time类中声明Date类为其友元类,那么可以在Date类中直接访问Time类的私有成员变量,但想在Time类中访问Date类中私有的成员变量则不行。
3. 友元关系不能传递,如果C是B的友元, B是A的友元,则不能说明C时A的友元。
4. 友元关系不能继承,在继承位置再给大家详细介绍。

class Time
{
	friend class Date; // 声明日期类为时间类的友元类,则在日期类中就直接访问Time类
	//中的私有成员变量
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;
};

5. 内部类和匿名对象

5.1 内部类

概念:

1. 如果一个类定义在另一个类的内部,这个内部类就叫做内部类。内部类是一个独立的类,
它不属于外部类,更不能通过外部类的对象去访问内部类的成员。外部类对内部类没有任何优越的访问权限。
2. 注意:内部类就是外部类的友元类,参见友元类的定义,内部类可以通过外部类的对象参数来访问外部类中的所有成员。但是外部类不是内部类的友元。

特性:

1. 内部类可以定义在外部类的public、protected、private都是可以的。
2. 注意内部类可以直接访问外部类中的static成员,不需要外部类的对象/类名。
3. 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::B b;
	b.foo(A());
	return 0;
}

5.2 匿名对象

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);
    return 0;
}


         以上就是我对类于对象的全部补充啦,还请大家多多支持咯!

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

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

相关文章

python|第五章考试题及练习题

本篇文章是对北京理工大学嵩天老师的《Python语言程序设计》第五章考试题及练习题的学习记录。 一、考试题 1、随机密码生成 问题描述&#xff1a; 描述 补充编程模板中代码&#xff0c;完成如下功能&#xff1a;‪‬‪‬‪‬‪‬‪‬‮‬‪‬‫‬‪‬‪‬‪‬‪‬‪‬‮‬‪…

Altium Designer 18中原理图DRC编译和PCB DRC检查-AD DRC

一、原理图编译 原理图检查的主要内容有&#xff1a; 1、元件位号冲突。也即多个元件编号相同&#xff0c;例如两个电容在原理图中都被命名为C2&#xff0c;显然肯定是无法生成PCB的。 2、网络悬浮。也即网络标号没有附着在电气走线上&#xff0c;一般这种是人操作失误&…

【Azure 架构师学习笔记】-Azure Logic Apps(7)- 自定义Logic Apps 调度

本文属于【Azure 架构师学习笔记】系列。 本文属于【Azure Logic Apps】系列。 接上文【Azure 架构师学习笔记】-Azure Logic Apps&#xff08;6&#xff09;- Logic Apps调用ADF 前言 在稍微了解Logic Apps的使用之后&#xff0c;需要开始考虑如何调度起来。在Logic Apps里面…

ChatGPT会让6个低端岗位失业

​我是卢松松&#xff0c;点点上面的头像&#xff0c;欢迎关注我哦&#xff01; 其实最近我们也是研究探索ChatGPT的商业应用方向&#xff1a;比如账号买卖、卖文章、论文、脚本文案、卖使用次数和时长、OEM系统等等。 目前来看&#xff0c;ChatGPT会让一部分低端的岗位失业&…

GEE学习笔记 七十八:干涸的洪泽湖

今天看了一篇报道直击60年一遇气象干旱&#xff1a;洪泽湖缩小近一半&#xff0c;鱼蟹受灾严重&#xff01;_新华报业网&#xff08;直击60年一遇气象干旱&#xff1a;洪泽湖缩小近一半&#xff0c;鱼蟹受灾严重&#xff01;&#xff09;&#xff0c;既然玩GEE那就要玩出点花样…

图学习——03预备知识

本章我们介绍关于图的基础知识&#xff0c;包括图的定义、类型和性质、图谱理论、图的傅里叶分析等。在之后介绍图神经网络会基于这些基础知识展开&#xff0c; 想要简单运用图神经网络&#xff0c;这部分知识可以不用学。想要系统的理解图神经网络的来源和本质&#xff0c;这…

2019蓝桥杯真题年号字串(填空题) C语言/C++

题目描述 本题为填空题&#xff0c;只需要算出结果后&#xff0c;在代码中使用输出语句将所填结果输出即可。 小明用字母 A 对应数字 11&#xff0c;B 对应 2&#xff0c;以此类推&#xff0c;用 Z 对应 26。对于 27 以上的数字&#xff0c;小明用两位或更长位的字符串来对应&…

在Windows10上安装虚拟机---VMware 17 Pro下载与安装

在Windows10上安装虚拟机---VMware下载与安装0 前言1 下载VMware 17 pro2 安装VMware 17 Pro3. 打开Vmware0 前言 电脑原生系统&#xff1a;Windows10虚拟机软件&#xff1a;VMware 17 pro准备好安装虚拟机的文件夹路径 1 下载VMware 17 pro 下载网址&#xff1a;VMware 官网…

采用工时表软件能为企业员工带来哪些好处

如今社会人力成本一直在攀升&#xff0c;企业对提升人员工作饱和度和有效分配和利用人力资源非常在意&#xff0c;而工时表软件是现代被广泛应用于各行业领域&#xff0c;用来解决项目及非项目上的人员时效管理问题的手段之一。 企业员工工时管理不一定是一件麻烦事。采用像8M…

12.centos7部署sonarqube9.6

12.centos7部署sonarqube9.6环境&#xff1a;sonarqube9.6Postgresql13JDK11sonarqube9.6下载地址&#xff1a;Postgresql13 rpm下载地址&#xff1a;JDK11下载地址&#xff1a;准备工作&#xff1a;修改文件句柄数&#xff08;最大文件数&#xff09;和用户最大进程数限制修改…

图扑数字孪生智慧机场,助推民航“四型机场“建设

前言 民航局印发的《智慧民航建设路线图》文件中&#xff0c;明确提出智慧机场是智慧民航的四个核心抓手之一。并从机场全域协同运行、作业与服务智能化、智慧建造与运维方面&#xff0c;为智慧机场的发展绘制了清晰的蓝图。 效果展示 图扑软件应用自主研发核心产品 HT for …

Vue-Router 介绍及路由原理分析

文章目录Vue-Router 路由模式单页面与传统页面跳转的区别Hash 模式History 模式abstract 模式原理解析Hash 模式原理History 模式原理路由使用引入 Vue-Router获取全局路由跳转参数的变化获取路由中带的参数重定向页面Vue-Router 路由模式 单页面与传统页面跳转的区别 单页面…

Spark Catalyst 查询优化器原理

这里我们讲解一下SparkSQL的优化器系统Catalyst&#xff0c;Catalyst本质就是一个SQL查询的优化器&#xff0c;而且和 大多数当前的大数据SQL处理引擎设计基本相同&#xff08;Impala、Presto、Hive&#xff08;Calcite&#xff09;等&#xff09;。了解Catalyst的SQL优化流程&…

Spring 入门教程详解

✅作者简介&#xff1a;2022年博客新星 第八。热爱国学的Java后端开发者&#xff0c;修心和技术同步精进。 &#x1f34e;个人主页&#xff1a;Java Fans的博客 &#x1f34a;个人信条&#xff1a;不迁怒&#xff0c;不贰过。小知识&#xff0c;大智慧。 &#x1f49e;当前专栏…

Web3中文|1月数据显示复苏迹象,涉及NFT、DeFi、Dapp、链游……

本期看点 1、Dapp行业概述 2、DeFi的TVL增长26.8%&#xff0c;有回暖迹象 3、NFT市场数据飙升&#xff0c;交易额达9.46亿美元 4、链游使用量占行业48% 5、与去年相比&#xff0c;1月份区块链漏洞损失最低 区块链领域正在多元化发展&#xff0c;2023年1月&#xff0c;从各…

从一致性角度考虑推荐冷启动长尾推荐问题(二)

前言&#xff1a;在推荐系统中user&item emb往往是最重要的特征之一&#xff0c;在冷启动和长尾优化的工作中&#xff0c;往往也是优化的重点&#xff0c;相当一部分工作是围绕着emb优化展开&#xff0c;所以这里单独开了一章。4)emb分布一致性主要思路在于冷启内容emb和高…

PTP GPTP芯片资料翻译88E6352

88E6352应用 网关 车载信息娱乐 车身域控制器 PTP PTP通过周期型地交换控制包实现 选择其中网络最佳质量时钟元素&#xff0c;作为PTP网络中Grand Master.没有Grand Master 节点变成PTP slave节点。PTP节点从Grand Master节点获得他们驱动频率和时间信息。 基本观念是PTP帧…

21- 朴素贝叶斯 (NLP自然语言算法) (算法)

朴素贝叶斯要点 概率图模型算法往往应用于NLP自然语言处理领域。根据文本内容判定 分类 。 概率密度公式&#xff1a; 高斯朴素贝叶斯算法: from sklearn.naive_bayes import GaussianNB model GaussianNB() model.fit(X_train,y_train) 伯努利分布朴素贝叶斯算法 fro…

袋鼠云产品功能更新报告04期丨2023年首次,产品升级“狂飙”

新的一年我们加紧了更新迭代的速度&#xff0c;增加了数据湖平台EasyLake和大数据基础平台EasyMR&#xff0c;超40项功能升级优化。我们将继续保持产品升级节奏&#xff0c;满足不同行业用户的更多需求&#xff0c;为用户带来极致的产品使用体验。 以下为袋鼠云产品功能更新报…

java学习----网络编程

网络编程入门 网络编程概述 计算机网络 ​ 计算机网络是指地理位置不同的具有独立功能的计算机及其外部设备&#xff0c;通过通信线路连接起来&#xff0c;在网络操作系统&#xff0c;网络管理软件及网络通信协议的管理协调下&#xff0c;实现资源共享和信息传递的计算机系统…