【C++练级之路】【Lv.4】类和对象(下)(初始化列表,友元,static成员,编译器的优化)

目录

  • 一、再谈构造函数
    • 1.1 构造函数体赋值
    • 1.2 初始化列表
    • 1.3 explicit关键字
  • 二、static成员
    • 2.1 概念
    • 2.2 特性
  • 三、友元
    • 3.1 引入
    • 3.2 友元函数
      • 3.2.1 概念
      • 3.2.2 特性
    • 3.3 友元类
      • 3.3.1 概念
      • 3.3.2 特性
  • 四、内部类
    • 4.1 概念
    • 4.2 特性
  • 五、匿名对象
  • 六、编译器的优化
    • 6.1 传参优化
      • 6.1.1 传值传参
      • 6.1.2 传引用传参
    • 6.2 返回优化
      • 6.2.1 创建对象再返回
      • 6.2.2 返回匿名对象
    • 6.3 优化总结
      • 6.3.1 函数传参
      • 6.3.2 对象返回
  • 七、再次理解封装
  • 总结

欢迎各位小伙伴关注我的专栏,和我一起系统学习C++,共同探讨和进步哦!

学习专栏

《进击的C++》


一、再谈构造函数

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

虽然上述构造函数调用之后,对象中已经有了一个初始值,但是不能将其称为对对象中成员变量的初始化,构造函数体中的语句只能将其称为赋初值,而不能称作初始化。因为初始化只能初始化一次,而构造函数体内可以多次赋值

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. 类中包含以下成员,必须放在初始化列表位置进行初始化:
    • 引用成员变量
    • const成员变量
    • 自定义类型成员(且该类没有默认构造函数时)
class Time
{
public:
	Time(int hour)
		: _hour(hour)
	{}
private:
	int _hour;
};

class Date
{
public:
	Date(int year, int month, int day)
		: _year(year)
		, _month(month)
		, _day(day)
		, _t(10)
	{}
private:
	int _year;
	const int _month;//const
	int& _day;//引用
	Time _t;//没有默认构造函数
};
  1. 无论是否显式使用初始化列表,成员变量都在初始化列表定义。所以,尽量使用初始化列表初始化
  2. 成员变量在类中声明次序就是其在初始化列表中的初始化顺序,与其在初始化列表中的先后次序无关。

请思考下面代码的运行结果:

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  随机值

1.3 explicit关键字

先看看以下两种初始化的方式:

class Time
{
public:
	Time(int hour)
		: _hour(hour)
	{}
private:
	int _hour;
};

int main()
{
	Time t1(4);
	Time t2 = 4;
	return 0;
}

第一种方式毫无疑问是构造函数,而第二种方式是构造函数+拷贝构造。先构造出t2对象,再将4(int)隐式类型转换成(Time),最后拷贝给t2。但是,在较新的编译器下,会将该过程直接优化成带参构造

C++98中,只允许对于单个参数或者除第一个参数无默认值其余均有默认值的构造函数隐式类型转换。

C++11中,增加了可对多参构造函数隐式类型转换,代码如下:

class Time
{
public:
	Time(int hour, int minute)
		: _hour(hour)
		, _minute(minute)
	{}
private:
	int _hour;
	int _minute;
};

int main()
{
	Time t1(4, 5);
	Time t2 = { 4,5 };
	return 0;
}

而用explicit修饰构造函数,将会禁止构造函数的隐式转换

class Time
{
public:
	explicit Time(int hour)
		: _hour(hour)
	{}
	explicit Time(int hour, int minute)
	: _hour(hour)
	, _minute(minute)
	{}
private:
	int _hour;
	int _minute;
};

int main()
{
	Time t1(4);
	Time t2 = 4;//err
	Time t3(4, 5);
	Time t4 = { 4,5 };//err
	return 0;
}

二、static成员

2.1 概念

用static修饰的成员变量,称之为静态成员变量;用static修饰的成员函数,称之为静态成员函数

2.2 特性

  1. 静态成员属于整个类,存放在静态区,而不属于某一个具体的对象
  2. 静态成员变量必须在类外定义,定义时不添加static关键字,类中只是声明
  3. 静态成员函数没有隐藏的this指针,不能访问任何非静态成员

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

方法一:定义全局变量

因为std标准库中,有一个count的函数,所以防止命名冲突,局部展开。

using std::cout;
using std::endl;

int count = 0;//全局变量

class A
{
public:
	A(int a = 4)
		: _a(a)
	{
		++count;
	}
	A(const A& a)
		: _a(a._a)
	{
		++count;
	}
private:
	int _a;
};

int main()
{
	A a1;
	A a2 = 5;
	A a3(3);

	cout << count << endl;
	return 0;
}

但是,这种方法也有很大缺陷,如果我不小心在外部直接对count进行修改,那结果就不对了。

int main()
{
	A a1;
	A a2 = 5;
	A a3(3);
	++count;
	++count;
	
	cout << count << endl;
	return 0;
}

方法二:定义成员变量

但是,如果定义为成员变量,就属于某个具体的类了。所以,我们加上static关键字,让其属于整个类。

因为静态成员变量属于整个类,所以不在初始化列表中定义,而是在类外定义。

class A
{
public:
	A(int a = 4)
		: _a(a)
	{
		++count;
	}
	A(const A& a)
		: _a(a._a)
	{
		++count;
	}
	static int GetCount()//静态成员函数
	{
		return count;
	}
private:
	int _a;
	static int count;//静态成员声明
};

int A::count = 0;//静态成员定义

int main()
{
	A a1;
	A a2 = 5;
	A a3(3);

	cout << A::GetCount() << endl;
	return 0;
}
  • 因为count变成private类型的成员变量,所以外界无法直接访问,就要通过成员函数来获取count。
  • 但是,调用非静态成员函数需要一个对象,所以为了简便,我们可以调用静态成员函数,这样就可以通过类域直接访问。

【问题】

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

三、友元

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

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

3.1 引入

当我们要重载流插入操作符<<时,如果定义为成员函数时,不符合常规写法逻辑:

class Date
{
public:
	Date(int year = 2000, int month = 1, int day = 1)
		: _year(year)
		, _month(month)
		, _day(day)
	{}
	ostream& operator<<(ostream& out)
	{
		out << _year << "/" << _month << "/" << _day << endl;
		return out;
	}
	istream& operator>>(istream& in)
	{
		in >> _year >> _month >> _day;
		return in;
	}
private:
	int _year;
	int _month;
	int _day;
};

int main()
{
	Date d;
	d >> cin;//不符合常规写法逻辑
	d << cout;
	return 0;
}

这是因为cout的输出流对象和隐含的this指针在抢占第一个参数的位置。this指针默认是第一个参数也就是左操作数了。但是实际使用中cout需要是第一个形参对象,才能正常使用。

所以,我们只能将其重载为全局函数。但是,全局函数又没办法访问private成员,所以就用友元来解决。

3.2 友元函数

3.2.1 概念

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

class Date
{
	friend ostream& operator<<(ostream& out, const Date& d);
	friend istream& operator>>(istream& in, Date& d);
public:
	Date(int year = 2000, int month = 1, int day = 1)
		: _year(year)
		, _month(month)
		, _day(day)
	{}
private:
	int _year;
	int _month;
	int _day;
};

ostream& operator<<(ostream& out, const Date& d)
{
	out << d._year << "/" << d._month << "/" << d._day << endl;
	return out;
}
istream& operator>>(istream& in, Date& d)
{
	in >> d._year >> d._month >> d._day;
	return in;
}

int main()
{
	Date d;
	cin >> d;
	cout << d;
	return 0;
}

3.2.2 特性

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

3.3 友元类

3.3.1 概念

在一个类中,用friend修饰的类是友元类。友元类的所有成员函数都可以是另一个类的友元函数,都可以访问另一个类中的非公有成员。

3.3.2 特性

  • 友元关系是单向的,不具有交换性。
    如果A是B的友元,不能说明B是A的友元。
  • 友元关系不能传递
    如果C是B的友元, B是A的友元,则不能说明C时A的友元。
  • 友元关系不能继承,在继承位置再给大家详细介绍。
class Time
{
	friend class Date;//友元类
public:
	Time(int hour = 0, int minute = 0, int second = 0)
		: _hour(hour)
		, _minute(minute)
		, _second(second)
	{}
	void Print()
	{}
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;
};

简单理解:友元类可以访问定义友元的类,而定义友元的类不能访问友元类。

四、内部类

4.1 概念

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

注意内部类就是外部类的友元类

4.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());//传参A的匿名对象
    
    return 0;
}

注意:其他类不能访问内部类,只有对应的外部类才能访问内部类。

五、匿名对象

对象可以匿名,就像结构体匿名一样。

写法:类名()

通常情况下,我们只想使用一次对象,就会使用匿名对象。

class Solution
{
public:
	int Sum_Solution(int n)
	{
		//...
		return n;
	}
};
int main()
{
	// 匿名对象在这样场景下就很好用,当然还有一些其他使用场景,这个我们以后遇到了再说
	cout << Solution().Sum_Solution(10) << endl;
	return 0;
}

六、编译器的优化

先写一个测试的类A

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

6.1 传参优化

6.1.1 传值传参

优化一般只在同一表达式进行,不能跨表达式优化。

void f1(A a)
{}

int main()
{
	A a1 = 1;//构造+拷贝构造——》优化为构造
	f1(a1);//无优化,只能在同一表达式进行优化

	f1(2);//构造+拷贝构造——》优化为构造
	f1(A(3));//构造+拷贝构造——》优化为构造
	return 0;
}

6.1.2 传引用传参

由于传引用本身就是最简,所以不需要优化。

void f2(const A& a)
{}

int main()
{
	A a1 = 1;//构造+拷贝构造——》优化为构造

	f2(a1);//无优化
	f2(2);//无优化
	f2(A(3));//无优化
	return 0;
}

6.2 返回优化

6.2.1 创建对象再返回

A f3()
{
	A a;
	return a;
}

int main()
{
	f3();//无优化,因为在不同表达式
	A a1 = f3();//构造+两次拷贝构造——》优化为构造+拷贝构造

	A a3;
	a3 = f3();//思考这种写法与上述写法的区别
	return 0;
}

6.2.2 返回匿名对象

A f4()
{
	return A();
}

int main()
{
	f4();//构造+拷贝构造——》优化为构造
	A a2 = f4();//构造+两次拷贝构造——》优化为构造
	return 0;
}

6.3 优化总结

6.3.1 函数传参

  • 尽量使用const &传参

6.3.2 对象返回

  1. 接收返回对象,尽量以拷贝构造方式接收,不要赋值接收
  2. 函数中返回对象时,尽量返回匿名对象

七、再次理解封装

现实生活中的实体计算机并不认识,计算机只认识二进制格式的数据。如果想要让计算机认识现实生活中的实体,用户必须通过某种面向对象的语言,对实体进行描述,然后通过编写程序,创建对象后计算机才可以认识。

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

总结

至此,类和对象章节结束。而从此刻开始,才算是真正进入C++的大门!之所以有人说C++难学,大概就是前期门槛较高,语法繁杂。但是,此刻迈过这道坎,便是轻舟已过万重山。

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

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

相关文章

Java EE 网络之网络初识

文章目录 1. 网络发展史1.1 独立模式1.2 网络互连1.3 局域网 LAN1.4 广域网 WAN 2. 网络通信基础2.1 IP 地址2.2 端口号2.3 认识协议2.4 五元组2.5 协议分层2.5.1 什么是协议分层2.5.2 分层的作用2.5.3 OSI七层协议2.5.4 TCP/IP五层协议2.5.5 网络设备所在分层 2.6 分装和分用 …

简单的教务系统

#include <stdio.h> #include <string.h> #define N 20 int i,j,n,m,lll0,renshu6; double zcj[N]{0};struct stu{ char num[10]; //学号char name[10]; //姓名char sex; //姓别double score[3]; //3 门课的成绩double sum; //3 门课的总分double aver; //3 门课的…

【深度学习目标检测】五、基于深度学习的安全帽识别(python,目标检测)

深度学习目标检测方法则是利用深度神经网络模型进行目标检测&#xff0c;主要有以下几种&#xff1a; R-CNN系列&#xff1a;包括R-CNN、Fast R-CNN、Faster R-CNN等&#xff0c;通过候选区域法生成候选目标区域&#xff0c;然后使用卷积神经网络提取特征&#xff0c;并通过分类…

超聚变服务器(原华为服务器)网站模拟器

一、超聚变服务器&#xff08;原华为服务器&#xff09;网站模拟器&#xff1a; 原来了解服务器可以从他的网站上进行了解&#xff0c;模拟器做的很好了。 https://support.xfusion.com/server-simulators/ 有很多的模拟器&#xff0c;今天主要看下BMC的设置 有很多的在线工具…

vue中element-ui日期选择组件el-date-picker 清空所选时间,会将model绑定的值设置为null 问题 及 限制起止日期范围

一、问题 在Vue中使用Element UI的日期选择组件 <el-date-picker>&#xff0c;当你清空所选时间时&#xff0c;组件会将绑定的 v-model 值设置为 null。这是日期选择器的预设行为&#xff0c;它将清空所选日期后将其视为 null。但有时后端不允许日期传空。 因此&#xff…

生产实践:基于K8S的私有化部署解决方案

随着国内数字化转型的加速和国产化进程推动&#xff0c;软件系统的私有化部署已经成为非常热门的话题&#xff0c;因为私有化部署赋予了企业更大的灵活和控制权&#xff0c;使其可以根据自身需求和安全要求定制和管理软件系统。下面分享下我们的基于k8S私有化部署经验。 私有化…

AR眼镜光学方案_AR眼镜整机硬件定制

增强现实(Augmented Reality&#xff0c;AR)技术通过将计算机生成的虚拟物体或其他信息叠加到真实世界中&#xff0c;实现对现实的增强。AR眼镜作为实现AR技术的重要设备&#xff0c;具备虚实结合、实时交互的特点。为了实现透视效果&#xff0c;AR眼镜需要同时显示真实的外部世…

【WebRTC】用WebRTC做即时视频聊天应用

【配套项目源码】 打开即用,设置一个免费的Agora账户就可以实现视频电话。非常好的WebRTC学习和应用项目。 用VSCode打开即可。 https://download.csdn.net/download/weixin_41697242/88630069 【什么是WebRTC?】 WebRTC是一套基于JS的API,能够建立端对端的直接通信,实…

服务器上配置jupyter,提示Invalid credentials如何解决

我是按照网上教程在服务器上安装的jupyter以及进行的密码配置&#xff0c;我利用 passwd()这个口令生成的转译密码是"argon...."。按照教程配置jupyter notebook配置文件里面的内容&#xff0c;登陆网页提示"Invalid credentials"。我谷歌得到的解答是&…

如何学习Kubernetes,学习K8S入门教程

学习 Kubernetes&#xff08;K8s&#xff09;确实不容易 你的硬件资源有限时&#xff0c;不过别担心&#xff0c;我帮你理清思路&#xff0c;让你在学习 K8s 的路上更加从容。 1、资源限制下的学习方法 当硬件资源有限时&#xff0c;一个好的选择是使用云服务提供的免费层或者…

✺ch2——OpenGL图像管线

目录 基于C图形应用&#xff06;管线概览OpenGL类型第一个C/OpenGL应用程序◍API (1) GLSL类型着色器——画一个点的程序◍API (2)◍API (3) 栅格化像素操作——Z-buffer算法检测 OpenGL 和 GLSL 错误◍API (4) 从顶点来构建一个三角形场景动画◍API (5) OpenGL某些方面的数值—…

蓝牙物联网智慧物业解决方案

蓝牙物联网智慧物业解决方案是一种利用蓝牙技术来提高物业管理和服务效率的解决方案。它通过将蓝牙技术与其他智能设备、应用程序和云服务相结合&#xff0c;为物业管理和服务提供更便捷、高效和智能化的支持。 蓝牙物联网智慧物业解决方案包括&#xff1a; 1、设备管理&#…

【Qt QML入门】TextInput

TextInput&#xff1a;单行文本输入框。 TextInput除了光标和文本外&#xff0c;默认没有边框等效果。 import QtQuick import QtQuick.Window import QtQuick.ControlsWindow {id: winwidth: 800height: 600visible: truetitle: qsTr("Hello World")//单行文本输…

Vue2.x源码:new Vue()做了啥

例子1new Vue做了啥?new Vue做了啥,源码解析 initMixin函数 初始化 – 初始化Vue实例的配置initLifecycle函数 – 初始化生命周期钩子函数initEvents – 初始化事件系统初始化渲染 initRender初始化inject选项 例子1 <div id"app"><div class"home&…

C_11练习题答案

一、单项选择题(本大题共20小题,每小题2分,共40分。在每小题给出的四个备选项中,选出一个正确的答案,并将所选项前的字母填写在答题纸的相应位置上。) 以下叙述中正确的是(C)A.C语言不是一种高级语言 B.C语言不用编译就能被计算机执行 C.C语言能够直接访问物理地址和进行位…

计算机设计大赛信息可视化设计的获奖经验剖析解读—基于本专栏文章助力4C大赛【全网最全万字攻略-获奖必读】

文章目录 一.中国大学生计算机设计大赛1.1赛道解读1.2 信息可视化设计小类介绍1.2 小类区别解读 二.信息可视化设计赛道获奖经验2.1 四小类作品预览2.1.1 数据可视化小类-优秀参赛作品展览2.1.2 信息图形设计小类-优秀参赛作品展览2.1.3 动态信息影像&#xff08;MG动画&#x…

css3 clip-path剪切图片

大致看了一下&#xff0c;反正以后用到就慢慢调吧 剪切四个角 clip-path: polygon(14px 0, calc(100% - 14px) 0, 100% 14px, 100% calc(100% - 14px), calc(100% - 14px) 100%, 14px 100%, 0 calc(100% - 14px), 0 14px); 三角形 clip-path: polygon(50% 0,0 100%, 100% 100…

ShardingSphere数据分片之读写分离

1、概述 读写分离是一种常见的数据库架构&#xff0c;它将数据库分为主从库&#xff0c;一个主库&#xff08;Master&#xff09;用于写数据&#xff0c;多个从库&#xff08;Slave&#xff09;进行轮询读取数据的过程。主从库之间通过某种通讯机制进行数据的同步。 所以&…

【视觉SLAM十四讲学习笔记】第四讲——李代数求导与扰动模型

专栏系列文章如下&#xff1a; 【视觉SLAM十四讲学习笔记】第一讲——SLAM介绍 【视觉SLAM十四讲学习笔记】第二讲——初识SLAM 【视觉SLAM十四讲学习笔记】第三讲——旋转矩阵 【视觉SLAM十四讲学习笔记】第三讲——旋转向量和欧拉角 【视觉SLAM十四讲学习笔记】第三讲——四元…

《PCL多线程加速处理》-滤波-统计滤波

《PCL多线程加速处理》-滤波-统计滤波 一、效果展示二、实现方式三、代码一、效果展示 提升速度随着点云越多效果越明显 二、实现方式 1、原始的统计滤波实现方式 #include <pcl/filters/statistical_outlier_removal.h>pcl::PointCloud<pcl::PointXYZ