<C++>类和对象下|初始化列表|explicit static|友元|内部类|匿名对象|构造函数的优化

文章目录

  • 1. 初始化列表
  • 2. explicit关键字
  • 3. 友元
    • 3.1 友元函数
    • 3.2 友元类
  • 4. static关键字
    • 4.1 概念
    • 4.2 特性
  • 5.内部类
    • 5.1 概念
    • 5.2 特性
  • 6. 匿名对象
  • 7. 拷贝构造时的优化

1. 初始化列表

在类的构造函数体中,对成员属性写的操作叫做赋值,那么成员的初始化是在哪里进行呢?

class Date
{
public:
	Date(int year = 1, int month = 1, int day = 1)
	{
		//以下全部都是赋值,不是初始化
		_year = year;
		_month = month;
		_day = day;
	}
private:
    	//以下全部都是声明
	int _year;
	int _month;
	int _day;
};

那我们定义对象时,成员属性是在那里定义的呢?
成员属性在初始化列表中定义。
初始化列表:以一个冒号开始,接着是一个以逗号分隔的数据成员列表,每个成员变量后面跟一个放在括号中的初始值或表达式

	Date(int year = 1, int month = 1, int day = 1)
		//成员变量在初始化列表中定义
		:_year(year)
		,_month(month)
		,_day(day)
	{
		//以下全部都是赋值,不是初始化
		_year = year;
		_month = month;
		_day = day;
	}

上述是我们显示写的初始化列表,若没有显示写,初始化列表会将内置类型变量设为初始值

image-20230730095521005

显示写初始化列表image-20230730095732735

注意:

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

  2. 类中若包含以下成员,必须放在初始化列表中进行初始化

    • 引用成员变量

    • const成员变量

    • 无默认构造函数的自定义类型成员变量

      引用和const变量都需要在定义时初始化,若自定义类型对象无默认构造函数则必须在初始化列表中显示传参调用构造函数。

总结: 成员属性的初始化是在初始化列表中完成的,若没有写初始化列表则默认以随机值初始化内置类型,自定义类型变量若有默认构造函数则会调用默认构造函数;构造函数体内完成的是对成员属性的二次赋值

**注意:**C++11打的补丁在声明时为变量设置缺省值,本质上就是在初始化列表中为成员设置初始值。


2. explicit关键字

class A
{
public:
	//单参数的构造函数可以发生隐式类型转换
	 A(int a) :_a(a)
	{	
		cout << "A(int a)\n";
	}
private:
	int _a;
};
int main()
{
	A a1(1);//调用构造函数
    //类型不匹配时内置类型会隐式转换为自定义类型 即1转换为A(1) 再通过拷贝构造函数用A(1)构造a3
    //支持类型转换的前提是A具有单参数构造函数
	A a3 = 1
        const A& ref = 1;//将ref绑定构造出来的临时对象,延长了临时对象的生命周期
} 

如果加上explicit关键字则不会发生隐式类型转换(不影响显式类型转换)image-20230730105707505

对于多参数构造函数,C++98及以前不支持隐式类型转换,C++11以后支持了

class B
{
public:
	//C++11支持多参数构造函数的隐式转换
	B(int b1, int b2)
		: _b1(b1)
		, _b2(b2)
	{
		cout << "B(int,int)\n";
	}
private:
	int _b1;
	int _b2;
};
int main()
{
	B b1(1, 2);//构造函数
	B b2 = { 1,2 };//隐式类型转换为B tmp(1,2),在将tmp拷贝给b2,编译器可能会优化为直接构造
        const B& rb  = {1, 2};//rb引用的是临时对象tmp(1,2)
	return 0;
}

运行结果image-20230730110044549


3. 友元

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

友元分为:友元函数友元类

3.1 友元函数

当我们想要重载操作符<<时,没有办法重载为成员函数,因为成员函数的第一个操作数为this指针,因此<<的左操作数不是cout,解决该方法只有将<<重载为全局函数,重载为全局函数时第一个参数类型为ostream&,第二个参数就是需要操作的对象类型,举例Date类<<运算符重载的定义应该是如下

ostream& operator<<(ostream& out, const Date& date)
{
    out << date._year << "/" << date._month << date._day << endl;
}

这里我们需要在函数体中访问Date类的私有成员,可以将operator<<定义为Date类的友元函数

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

class Date
{
	friend ostream& operator<<(ostream& out, const Date& date);//声明为友元函数,该函数可以访问Date的私有成员
public:
	Date(int year = 2023, int month = 7, int day = 30)
		:_year(year)
		, _month(month)
		, _day(day)
	{

	}

private:
	int _year;
	int _month;
	int _day;
};

注意:

  1. 友元函数可访问类的私有和保护成员,但不是类的成员函数

  2. 友元函数不能用const修饰(没有this指针)

  3. 友元函数可以在类定义的任何地方声明,不受类访问限定符限制

  4. 一个函数可以是多个类的友元函数

  5. 友元函数的调用与普通函数的调用原理相同

3.2 友元类

A类在B类中被声明为友元的,称A类是B类的友元类,A类中可以访问B类的私有成员。

class Time
{
	friend class Date;
public:
	Time(int hour = 0, int minute = 0, int sec = 0)
		:_hour(hour)
		,_minute(minute)
		,_sec(sec)
	{
	}
private:
	int _hour;
	int _minute;
	int _sec;
};
class Date
{
	friend ostream& operator<<(ostream& out, const Date& date);//声明为友元函数,该函数可以访问Date的私有成员
public:
	Date(int year = 2023, int month = 7, int day = 30)
		:_year(year)
		, _month(month)
		, _day(day)
	{
	}
	void SetTime(int hour, int minute, int sec)
	{
		//访问Time类的私有成员必须将Date类声明为Time类的友元类
		_t._hour = hour;
		_t._minute = minute;
		_t._sec = sec;
	}
private:
	int _year;
	int _month;
	int _day;
	Time _t;
};

注意:

  • 友元关系是单向的,不具有交换性。比如上述Time类和Date类,在Time类中声明Date类为其友元类,那么可以在Date类中直接访问Time类的私有成员变量,但想在Time类中访问Date类中私有的成员变量则不行。
  • 友元关系不能传递如果C是B的友元, B是A的友元,则不能说明C时A的友元。
  • 友元关系不能继承,在继承位置再给大家详细介绍。
  • 友元是一种高耦合的状态,如果一个函数的成员改变了可能会影响到与之相关的友元函数

4. static关键字

4.1 概念

声明为static的成员称为类的静态成员,static修饰类成员属性则称该属性为静态成员变量static修饰类成员函数称该函数为静态成员函数

设计一个类,统计该类创建过多少个对象和当前存在的对象个数

class A
{
public:
	A(int a = 1)
	{
		m++;
		n++;
	}
	~A()
	{
		n--;
	}
	A(const A& a)
	{
		m++;
		n++;
	}
	static int m;//记录创建对象的个数
	static int n;//记录当前存在对象的个数
};
int A::m = 0;
int A::n = 0;
A fun(A tmp)
{
	return tmp;
}
int main()
{
	A a1(1);
	A a2(2);
	fun(a1);
	cout << A::m <<" " << A::n << endl;//访问静态成员变量时需要指定类域
}

上述设计可以完成任务,但是静态成员变量m和n是public的,因此我们在类外部可以直接修改导致结果误差,可以将static成员属性设置为private对外部提供一个静态成员函数来获取静态成员变量

class A
{
public:
	A(int a = 1)
	{
		m++;
		n++;
	}
	~A()
	{
		n--;
	}
	A(const A& a)
	{
		m++;
		n++;
	}
	static int GetM()
	{
		return m;
	}
	static int GetN()
	{
		return n;
	}
private:
	static int m;//记录创建对象的个数
	static int n;//记录当前存在对象的个数
};
int A::m = 0;
int A::n = 0;
A fun(A tmp)
{
	return tmp;
}

int main()
{
	A a1(1);
	A a2(2);
	fun(a1);
	//cout << A::m <<" " << A::n << endl;
	cout << A::GetM() << " " << A::GetN() << endl;//调用静态成员函数时需要指定类域
}

4.2 特性

  1. 静态成员所有类对象所共享,不属于某个具体的对象,存放在静态区,类似于成员函数存放在公共代码区。
  2. 静态成员变量必须在类外部定义,定义时不添加static关键字,类中只是声明(因为静态变量不属于对象,所以不会调用构造函数在初始化列表中定义)
  3. 类静态成员名即可用类名::静态成员 或者 对象.静态名 来访问
  4. 静态成员没有this指针,不可以访问任何非静态成员
  5. sizeof不会计算静态成员的大小
  6. 静态成员也是类的成员,受public、protected、private访问限定符的限定
  7. 静态成员函数不可以调用非静态成员函数
  8. 非静态成员函数可以调用静态成员函数

5.内部类

5.1 概念

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

内部类是外部类的友元,内部类中可以通过外部类对象访问外部类的私有成员。外部类不是内部类的友元

class A
{
public:
	class B		//B是A的内部类
	{
	public:
		void fun(A& a)
		{
			a._a = 1;//B是A的友元,可以访问A的私有成员
			_b = 2;
			s_member = 2;//内部类和成员函数一样可以直接访问静态数据成员
		}
	private:
		int _b;
	};
	void SetA(int a)
	{
		_a = a;
	}
	int GetStaticMember()
	{
		return s_member;
	}
private:
	static int s_member;//声明静态成员变量
	int _a;
};
int A::s_member = 1;//静态成员变量定义在类的外部


int main()
{
	A a;
	a.SetA(10);
	A::B b;//想要使用内部类必须先指定外部类域
	b.fun(a);//内部类可以访问外部类类的private成员
	return 0;
}

静态成员变量不在对象中,因此静态成员变量不能在初始化列表中初始化,需要在类外部通过类域::变量名初始化

5.2 特性

  1. 内部类定义为public、protected、private都是可以的。
  2. 注意内部类可以直接访问外部类中的static成员,不需要外部类的对象/类名。
  3. 内部类只是被封装了(需要通过外部类访问),内部类不属于外部类
  4. 内部类是外部类的友元类

练习image-20230801094651169

class Solution {
public:
  class Sum//Sum是Solution类的友元,Sum内部可以访问Solution的私有成员
  {
       public:
       Sum()
       {
           _ret += _i;
           _i++;
       }
   };
   int Sum_Solution(int n) {
       Sum s[n];
       return _ret;
   }
   private:
   static int _ret;//Solution类的静态成员:用来记录结果
   static int _i;//Solution类的静态成员:用来记录当前加法因子
};
int Solution::_ret = 0;
int Solution::_i = 1;

6. 匿名对象

匿名对象是没有名字的对象,例如上述class A可以通过A();定义一个匿名对象,匿名对象的生命周期只有它所在的一行,定义完后立马会调用析构函数

class A
{
public:
	A(int a = 1)
	{
		_a = a;
		cout << "A()->" << _a << endl;
	}
	~A()
	{
		cout << "~A()->" << _a << endl;
	}
    void Print()
    {
        cout << "void Print()\n";
     }
private:
	int _a;
};

int main()
{
	A();//调用构造函数后立马调用析构函数
	return 0;
}

运行结果image-20230801095627239

匿名对象和正常对象一样可以调用函数、传参,仅仅生命周期与普通对象不同而已

匿名对象可以调用函数

	A().Print();

image-20230801100008300

匿名对象可以传参

void fun(const A& a)
{
	a.Print();
}
int main()
{
	//A();//调用构造函数后立马调用析构函数
	//A().Print();
	fun(A(2));
	return 0;
}

注意:匿名对象和临时对象一样具有常性,需要使用常引用来绑定匿名对象,相应的Print成员函数需要定义为const成员函数。


7. 拷贝构造时的优化

在传参和传返回值的过程中,一般编译器会做一些优化,减少对象的拷贝,这个在一些场景下还是非常有用的。

不同版本的编译器所作的优化不同,下面介绍主流编译器对于拷贝构造时常见的优化

同一个表达式中,连续的构造函数+构造函数/构造函数+拷贝构造函数/拷贝构造函数+拷贝构造函数会合并为一个构造函数/拷贝构造函数

class A
{
public:
	A(int a = 0)
		:_a(a)
	{
		cout << "A(int a)" << _a << endl;
	}
	A(const A& aa)
		:_a(aa._a)
	{
		cout << "A(const A& aa)" << _a << 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;
};
  • 构造函数+拷贝构造函数->构造函数

    int main()
    {
    	A a = 3;//构造+拷贝构造->构造 
    	return 0;
    }
    

    运行结果:
    image-20231118230421184

    编译器先用3构造临时对象,将临时对象拷贝构造给a,优化为直接用调用构造函数构造a

    void f1(A aa)
    {}
    int main()
    {
    	f1(A(2));//构造+拷贝构造->构造
    }
    
    
    image-20231118230855193
    void f1(A aa)
    {}
    int main()
    {
    	f1(3);//构造(隐式类型转换)+拷贝->构造
    }
    
    image-20231118231030261
  • 拷贝构造函数+拷贝构造函数->拷贝构造函数

    A f2()
    {
    	A aa(1);
    	return aa;//返回时会调用拷贝构造函数
    }
    int main()
    {
       A a = f2();//拷贝构造+拷贝构造->拷贝构造
       return 0;
    }
    
    image-20231118231234281 aa拷贝给临时变量,临时变量拷贝给a优化为aa拷贝给a

注意:若编译器太新或在release版本下对构造函数的优化可能更极端,可以跨表达式进行合并优化.


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

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

相关文章

深度学习数据集—细胞、微生物、显微图像数据集大合集

最近收集了一大波关于细胞、微生物、显微图像数据集&#xff0c;有细胞、微生物&#xff0c;细菌等。 接下来是每个数据的详细介绍&#xff01;&#xff01; 1、12500张血细胞增强图像&#xff08;JPEG&#xff09;数据集 该数据集包含12500张血细胞增强图像&#xff08;JPE…

实验(三):微程序计数器uPC实验

一、实验内容与目的 实验要求&#xff1a; 利用 CP226 实验仪上的 K16..K23 开关做为 DBUS 的数据&#xff0c;其它开关做为控制信号&#xff0c;实现微程序计数器 uPC 的写入和加1功能。 实验目的&#xff1a; 1、了解模型机中微程序的基本概念。 2、了解 uPC 的结构、工作原理…

windows nodejs 15.0.0下载安装

下载 Node v15.0.0 (Current) | Node.js (nodejs.org) 下载地址 https://nodejs.org/dist/v15.0.0/node-v15.0.0-x64.msi 安装 双击运行 等待安装完成 确认安装成功 管理员运行cmd 查看版本号

微信(小程序开发): 解决播放音乐没有声音的情况 代码不报错的情况下依旧没有声音的解决方案

解决无声的问题 在此之前&#xff0c;确保代码能够正常执行哈&#xff01;发这个其实没什么&#xff0c;就是有些人光写代码不调试出现了这个问题 其实解决方法特别简单 第一步&#xff1a; 打开项目后&#xff0c;点击三个点&#xff0c;然后选择模拟操作 第二步&#xff…

AIGC 是通向 AGI 的那条路吗?

AIGC 是通向 AGI 的那条路吗&#xff1f; 目录 一、背景知识 1.1、AGI&#xff08;人工通用智能&#xff09; 1.1.1、概念定义 1.1.2、通用人工智能特质 1.1.3、通用人工智能需要掌握能力 1.2、AIGC 二、AIGC 是通向 AGI 的那条路吗&#xff1f; 三、当前实现真正的 A…

python趣味编程-5分钟实现一个打字速度测试(含源码、步骤讲解)

Python速度打字测试是用 Python 编程语言编写的,速度打字测试 Python项目理念,我们将构建一个令人兴奋的项目,通过它您可以 检查 甚至 提高 您的打字速度。 为了创建图形用户界面(GUI),我们将使用 用于处理图形的pygame库。 Python 打字速度测试有利于学生或初学者提高…

基于动物迁徙算法优化概率神经网络PNN的分类预测 - 附代码

基于动物迁徙算法优化概率神经网络PNN的分类预测 - 附代码 文章目录 基于动物迁徙算法优化概率神经网络PNN的分类预测 - 附代码1.PNN网络概述2.变压器故障诊街系统相关背景2.1 模型建立 3.基于动物迁徙优化的PNN网络5.测试结果6.参考文献7.Matlab代码 摘要&#xff1a;针对PNN神…

问卷工具价格一览:合理定价,满足您的预算需求

在市场调研、市场营销和客户反馈收集等方面&#xff0c;问卷调查是一项重要而有效的工具。而在众多的问卷工具中&#xff0c;Zoho Survey以其丰富的功能和灵活的定价模式而备受关注。Zoho Survey的定价如何&#xff1f;今天我们来聊一聊。 Zoho Survey提供了多种定价方案&…

JRC Monthly Water History, v1.4数据集

简介&#xff1a; JRC Monthly Water History产品&#xff0c;是利用1984至2020年获取的landsat5、landsat7和landsat8的卫星影像&#xff0c;生成的一套30米分辨率的全球地表水覆盖的月度地表水监测地图集。该数据集共有442景数据&#xff0c;包含1984年3月至2020年12月间的月…

【心得】PHP的文件上传个人笔记

目录 1 php的文件上传绕过 黑名单绕过 2 php文件上传的00截断 3 iconv字符转换异常后造成了字符截断 4 文件后缀是白名单的时候的绕过 web服务器的解析漏洞绕过 5.高级文件上传绕过 1 .htaccess nginx.htaccess 2 服务端内容检测 3 配合伪协议来绕过 4.配合日志包含绕…

云课五分钟-0ALinux文件系统及权限-查询命令如何使用

前篇&#xff1a; 云课五分钟-09Linux基础命令实践-AI助力快速入门 视频&#xff1a; 云课五分钟-0ALinux文件系统及权限-查询命令如何使用 文本&#xff1a; Linux文件系统及权限示例教程&#xff08;Ubuntu&#xff09; 一、Linux文件系统基础 在Linux中&#xff0c;一切…

〖大前端 - 基础入门三大核心之JS篇㊲〗- DOM改变元素节点的css样式、HTML属性

说明&#xff1a;该文属于 大前端全栈架构白宝书专栏&#xff0c;目前阶段免费&#xff0c;如需要项目实战或者是体系化资源&#xff0c;文末名片加V&#xff01;作者&#xff1a;不渴望力量的哈士奇(哈哥)&#xff0c;十余年工作经验, 从事过全栈研发、产品经理等工作&#xf…

WordPress主题WoodMart v7.3.2 WooCommerce主题和谐汉化版下载

WordPress主题WoodMart v7.3.2 WooCommerce主题和谐汉化版下载 WoodMart是一款出色的WooCommerce商店主题&#xff0c;它不仅提供强大的电子商务功能&#xff0c;还与流行的Elementor页面编辑器插件完美兼容。 主题文件在WoodMart Theme/woodmart.7.3.2.zip&#xff0c;核心在P…

【SQL server】 表结构的约束和维护

表结构的约束和维护 修改表结构 (1)添加列 (2)删除列 (3)修改列alter table 表名 add 新列名 数据类型给员工表添加一列邮箱 alter table People add PeopleMail varchar(200)删除列 alter table People drop column PeopleMain修改列 alter table 表名 alter column 列名 数据…

基于海洋捕食者算法优化概率神经网络PNN的分类预测 - 附代码

基于海洋捕食者算法优化概率神经网络PNN的分类预测 - 附代码 文章目录 基于海洋捕食者算法优化概率神经网络PNN的分类预测 - 附代码1.PNN网络概述2.变压器故障诊街系统相关背景2.1 模型建立 3.基于海洋捕食者优化的PNN网络5.测试结果6.参考文献7.Matlab代码 摘要&#xff1a;针…

基于传统Session的登录

前言&#xff1a; 本人的一些简历上要回答的点。所以再此整理。 亮点&#xff1a; 使用Filter过滤器进行未登录状态自动跳转到登录页面的拦截&#xff0c;实现统一的权限管理。 1 登陆功能 1.1实体类和结果类 前端页面 约定 res.data.code为1时是登录成功。 数据库的empl…

DPAFNet:一种用于多模式脑肿瘤分割的残差双路径注意力融合卷积神经网络

DPAFNet: A Residual Dual-Path Attention-Fusion Convolutional Neural Network for Multimodal Brain Tumor Segmentation DPAFNet&#xff1a;一种用于多模式脑肿瘤分割的残差双路径注意力融合卷积神经网络背景贡献实验方法ulti-scale context feature extraction block&…

python趣味编程-5分钟实现一个益智数独游戏(含源码、步骤讲解)

Puzzle Game In Python是用 Python 编程语言Puzzle Game Code In Python编写的,有一个 4*4 的棋盘,有 15 个数字。然后将数字随机洗牌。 在本教程中,我将教您如何使用Python 创建记忆谜题游戏。 Python Puzzle Game游戏需要遵循以下步骤,首先是将图块数量移动到空的图块空…

浏览器黑暗模式插件

1.Opera浏览器本身黑暗主题 2.Chrome 3.Edge

【心得】PHP文件包含基本利用个人笔记

本文可能比较凌乱&#xff0c;快速总结保证自己看得懂&#xff08;真.个人笔记&#xff09; 文件包含的本质&#xff1a;代码复用、并行开发、模块化、增加移植性 include和eval的区别&#xff1a; include和eval一样&#xff0c;都不是函数&#xff0c;都是语言结构&#xf…