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

news2024/11/20 23:25:09

文章目录

  • 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.coloradmin.cn/o/1225352.html

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

相关文章

springBoot中starter

springBoot项目中引入starter 项目引入xxljob&#xff0c;仅需要导入对应的starter包&#xff0c;即可进行快速开发 <dependency><groupId>com.ydl</groupId><artifactId>xxl-job-spring-boot-starter</artifactId><version>0.0.1-SNAPS…

Ubuntu20.04 安装微信 【优麒麟的镜像源方式安装】

缺点&#xff1a;是网页版本的嵌入&#xff0c;功能少。 推荐wine方式安装&#xff1a;Ubuntu20.04 安装微信 【wine方式安装】推荐 从优麒麟的镜像源安装原生微信 应用下载-优麒麟&#xff5c;Linux 开源操作系统 新建文件software.list sudo vi /etc/apt/sources.list.d/…

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…

Golang起步篇(Windows、Linux、mac三种系统安装配置go环境以及IDE推荐以及入门语法详细释义)

Golang起步篇 Golang起步篇一. 安装Go语言开发环境1. Wondows下搭建Go开发环境(1). 下载SDK工具包(2). 解压下载的压缩包&#xff0c;放到特定的目录下&#xff0c;我一般放在d:/programs下(路径不能有中文或者特殊符号如空格等)(3). 配置环境变量步骤1&#xff1a;先打开环境变…

python自动化标注工具+自定义目标P图替换+深度学习大模型(代码+教程+告别手动标注)

省流建议 本文针对以下需求&#xff1a; 想自动化标注一些目标不再想使用yolo想在目标检测/语意分割有所建树计算机视觉项目想玩一玩大模型了解自动化工具了解最前沿模型自定义目标P图替换… 确定好需求&#xff0c;那么我们发车&#xff01; 实现功能与结果 该模型将首先…

【SQL server】数据库、数据表的创建

创建数据库 --如果存在就删除 --所有的数据库都存在sys.databases当中 if exists(select * from sys.databases where name DBTEST)drop database DBTEST--创建数据库 else create database DBTEST on --数据文件 (nameDBTEST,--逻辑名称 字符串用单引号filenameD:\DATA\DBT…

uni-app(1)pages. json和tabBar

第一步 在HBuilderX中新建项目 填写项目名称、确定目录、选择模板、选择Vue版本&#xff1a;3、点击创建 第二步 配置pages.json文件 pages.json是一个非常重要的配置文件&#xff0c;它用于配置小程序的页面路径、窗口表现、导航条样式等信息。 右键点击pages&#xff0c;按…

C语言进阶第十课 --------文件的操作

作者前言 &#x1f382; ✨✨✨✨✨✨&#x1f367;&#x1f367;&#x1f367;&#x1f367;&#x1f367;&#x1f367;&#x1f367;&#x1f382; ​&#x1f382; 作者介绍&#xff1a; &#x1f382;&#x1f382; &#x1f382; &#x1f389;&#x1f389;&#x1f389…

从多表连接视图对比人大金仓和Oracle

KING BASE 信息时代&#xff0c;数据是驱动业务决策和创新的核心资源。然而&#xff0c;随着数据量的不断增加&#xff0c;有效地处理和整合数据的过程变得愈发复杂。这时&#xff0c;多表连接视图悄然走进数据库世界&#xff0c;不仅能够将多个表中的数据整合在一起&#xff0…

代码随想录算法训练营第四十八天|121. 买卖股票的最佳时机 122.买卖股票的最佳时机II

文档讲解&#xff1a;代码随想录 视频讲解&#xff1a;代码随想录B站账号 状态&#xff1a;看了视频题解和文章解析后做出来了 121. 买卖股票的最佳时机 class Solution:def maxProfit(self, prices: List[int]) -> int:if len(prices) 0:return 0dp [[0] * 2 for _ in r…

二叉树前序,中序,后序遍历

前序遍历&#xff08;递归&#xff09;&#xff1a; 中序遍历&#xff08;递归&#xff09;&#xff1a;

2023全新付费进群系统源码 带定位完整版 附教程

这源码是我付费花钱买的分享给大家&#xff0c;功能完整。 搭建教程 Nginx1.2 PHP5.6-7.2均可 最好是7.2 第一步上传文件程序到网站根目录解压 第二步导入数据库&#xff08;58soho.cn.sql&#xff09; 第三步修改/config/database.php里面的数据库地址 第四步修改/conf…

qt-C++笔记之treeWidget初次使用

qt-C笔记之treeWidget初次使用 code review! 文章目录 qt-C笔记之treeWidget初次使用1.运行2.文件结构3.main.cpp4.widget.h5.widget.cpp6.widget.ui7.main.qrc8.qt_widget_test.pro9.options.png 1.运行 2.文件结构 3.main.cpp 代码 #include "widget.h"#include…

使用opera/火狐浏览器将网页固定到桌面和任务栏

1.单击Windows 图标&#xff0c;搜索Opera&#xff0c;右键单击它&#xff0c;然后选择Open file location 2.右键单击Opera&#xff0c;然后选择Show more options 3.将光标悬停在“发送到”选项上&#xff0c;然后选择“桌面&#xff08;创建快捷方式&#xff09;” 4.转到…

Android 弹出自定义对话框

Android在任意Activity界面弹出一个自定义的对话框&#xff0c;效果如下图所示: 准备一张小图片&#xff0c;右上角的小X图标64*64&#xff0c;close_icon.png&#xff0c;随便找个小图片代替&#xff1b; 第一步&#xff1a;样式添加&#xff0c;注意&#xff1a;默认在value…

leetcode系列(双语)003——GO无重复字符的最长子串

文章目录 003、Longest Substring Without Repeating Characters个人解题官方解题扩展 003、Longest Substring Without Repeating Characters 无重复字符的最长子串 Given a string s, find the length of the longest substring without repeating characters. 给定一个字符…

Linux非阻塞等待示例

Linux非阻塞等待实例 非阻塞等待的意义&#xff1a;简单的多进程编程示例代码解释 非阻塞等待的意义&#xff1a; 非阻塞等待在多进程编程中的意义主要体现在提高系统的响应性、实现异步任务执行、动态任务管理和多任务协同工作等方面。它允许父进程在等待子进程退出的同时&…

【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 列名 数据…

Vulkan渲染引擎开发教程 一、开发环境搭建

一 安装 Vulkan SDK Vulkan SDK 就是我们要搞的图形接口 首先到官网下载SDK并安装 https://vulkan.lunarg.com/sdk/home 二 安装 GLFW 窗口库 GLFW是个跨平台的小型窗口库&#xff0c;也就是显示窗口&#xff0c;图形的载体 去主页下载并安装&#xff0c;https://www.glfw.…