【C++深入浅出】类和对象下篇

news2024/11/20 18:07:49


一. 前言

        老样子,先来回顾一下上期的内容:上期我们着重学了C++类中的六大默认成员函数,并自己动手实现了一个日期类,相信各位对C++中的类已经有了一定程度的了解。本期就是类和对象的最后一篇啦,终于要结束咯,吧唧吧唧

        话不多说,开吃咯!!!

二. 初始化列表

2.1 引入

        我们先来看看下面的代码:

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

private:
	const int _year;
	const int _month;
	const int _day;
};
int main()
{
	Date d;
	return 0;
}

当我们编译代码时,发现编译器报了一大堆错误。报错的主要原因主要有两个

1、const变量定义时需要进行初始化

2、const变量不能作为左值

        欸,可能有些小伙伴就纳闷了:我们不是在构造函数中对const成员变量进行初始化了吗? 实际上,在构造函数函数体内进行的并不是初始化,而是赋值操作。因为初始化只能初始化一次,而构造函数体内可以进行多次赋值。

        出于这个原因,于是编译器就会报出以上两种错误。那怎么办呢?众所周知,初始化是在定义变量时进行的,那变量又是在哪定义的呢?答案是:初始化列表

2.2 概念

        在C++中,初始化列表可以认为是成员变量定义的地方。

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

class Date
{
public:
	Date(int year = 2023, int month = 1, int day = 1)
		:_year(year) //初始化列表,是每个成员变量定义的地方,可以进行初始化
		,_month(month) //用month的值初始化成员变量_month
		,_day(day)
	{}

private:
	//成员变量的声明
	const int _year = 0; 
	const int _month = 0;
	const int _day = 0;
};
int main()
{
	Date d;
	return 0;
}

2.3 注意事项

  1. 变量的初始化只能初始化一次,故每个成员变量在初始化列表中只能出现一次
  2. 当类中包含以下成员时,必须放在初始化列表位置进行初始化
    class A
    {
    public:
    	A(int a) //显式定义构造函数,不自动生成默认构造函数
    		:_a(a)
    	{}
    private:
    	int _a;
    };
    class B
    {
    public:
    	B(int a, int ref)
    		:_a(a) //调用有参构造函数初始化
    		, _ref(ref) //初始化引用变量
    		, _n(10) //初始化const变量
    	{}
    private:
    	A _a; // 没有默认构造函数的类
    	int& _ref; // 引用变量
    	const int _n; // const变量
    };
  3. 建议尽量使用初始化列表初始化,因为初始化列表是成员变量定义的地方,无论你是否显式地写,每个成员都要走初始化列表
    class Time
    {
    public:
    	Time(int hour = 0)
    		:_hour(hour)
    	{
    		cout << "Time()" << endl;
    	}
    private:
    	int _hour;
    };
    class Date1
    {
    public:
    	Date1(int day)
    		:_day(day)  //使用初始化列表进行初始化
    		,_t(day)
    	{}
    private:
    	int _day;
    	Time _t;
    };
    
    class Date2
    {
    public:
    	Date2(int day)
    	{
    		_day = day; //在构造函数内部进行赋值
    		_t = day;
    	}
    private:
    	int _day;
    	Time _t;
    };
    int main()
    {
    	Date1 d1(3);
    	cout << "-----------------------" << endl;
    	Date2 d2(3);
    	return 0;
    }

  4. C++11支持在声明处给缺省值,这个缺省值就是给初始化列表的。如果初始化列表没有显式给值,则使用这个缺省值;如果显式给了,就用给的值进行初始化。

  5. 初始化列表对成员变量的初始化顺序与其声明的次序相同,与初始化列表的先后次序无关。举个小例子

    class A
    {
    public:
    	A(int a)
    		:_a1(a)  //初始化列表的顺序和声明一样,即也是先初始化_a2再初始化_a1
    		, _a2(_a1)  //那么,这里用_a1初始化_a2会发生什么?_a1的值是多少
    	{}
    	void Print() {
    		cout << _a1 << " " << _a2 << endl;
    	}
    private:
    	//成员变量的声明,先_a2再_a1
    	int _a2;
    	int _a1;
    };
    int main() {
    	A aa(1);
    	aa.Print();
    }

    上面代码的输出结果是1 随机值

    解析:由于_a2的声明在_a1前,_a2会先于_a1进行初始化,因此_a2初始化时_a1还是个随机值,故_a2会被初始化为随机值,然后_a1再初始化为1。


    我们也可以使用调试来观察初始化顺序,如下所示:


三. explicit关键字

        构造函数不仅可以构造与初始化对象,对于单个参数或者除第一个参数无默认值其余均有默认值的构造函数,还具有隐式类型转换的作用,如下:

class Date
{
public:
    // 1. 单参构造函数,具有隐式类型转换作用
	Date(int year)
		:_year(year)
	{}

	//2. 虽然有多个参数,但是后两个参数可以不传递,具有类型转换作用
	//用explicit修饰构造函数,可以禁止类型转换
    //explicit Date(int year, int month = 1, int day = 1)
	//: _year(year)
	//, _month(month)
	//, _day(day)
	//{}
	
	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 d1(2022); //使用单参构造函数初始化d1

	// 用一个整形变量给日期类型对象赋值
	// 实际编译器背后会用2023构造一个匿名的临时对象,最后用这个临时对象给d1对象赋值
	d1 = 2023; 
	return 0;
}

像上面这种运算符左右两边类型不匹配,运算时编译器背后进行处理的过程,称之为隐式类型转换


但是,这样的代码往往可读性不好,我们更希望书写代码时左右两边的类型是一致的,那有没有什么办法可以禁止编译器进行隐式类型转换呢?有,就是explicit关键字

使用 explicit(显式的) 修饰构造函数,将会禁止构造函数的隐式类型转换。很简单,直接在构造函数前面加上explicit即可,这里就不再进行演示了。

四. static成员

4.1 概念

        用static修饰的成员变量称为静态成员变量;用static修饰的成员函数称之为静态成员函数。静态成员变量一定要在类外进行初始化。

class A
{
	static int GetCount() //静态成员函数
	{
		return count;
	}
private:
	static int count; //静态成员变量
};

int A::count = 10; //静态成员变量要在类外进行初始化

4.2 特性

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

小问题:静态成员函数可以调用非静态成员函数吗?反过来呢?


问题解答:答案是不行,静态成员函数不能调用非静态成员函数,因为静态成员函数没有隐藏的this指针,而非静态成员函数需要通过this指针来调用。但是非静态成员函数可以调用静态成员函数,因为静态成员函数的特点是没有this指针,故可以直接进行调用。


五. 友元

5.1 概念

        在C++中,为了封装性我们一般将成员变量声明为【private】私有的,只允许在类内访问成员变量。但是有时候我们需要在类外访问这些成员变量,此时有两种方法:1.将成员变量声明为【public】共有;2.利用友元

        友元提供了一种突破封装的方式,为代码的编写提供了便利。友元分为友元类友元函数,当一个函数/类声明为某个类的友元函数/类时,这个函数/类访问类中成员时不受访问限定符限制。下面是函数/类声明为友元的方式,用到了friend关键字

class A
{
	friend void GetCount(const A& a); //将全局函数GetCount声明为A类的友元函数
	friend class B; //将B类声明为A类的友元类
private:
	int count = 10;
	int num = 20;
};

class B
{
public:
	void GetNum(const A& a)
	{
		cout << a.num << endl; //b类中可以访问a类的私有成员
	}
};
void GetCount(const A& a)
{
	cout << a.count << endl; //可以访问A类的私有成员
}

int main()
{
	A a;
	B b;
	GetCount(a);
	b.GetNum(a);
	return 0;
}

小贴士:虽然友元提供了便利,但是友元会增加耦合度,破坏程序的封装性,故不建议使用友元。

5.2 友元函数   

        友元函数一般用作于流提取运算符>>以及流插入运算符<<的重载,这两个运算符的重载比较特殊,不能当做成员函数进行重载。

class Date
{
public:
	Date(int year = 2023, int month = 1, int day = 1)
		:_year(year)
		, _month(month)
		, _day(day)
	{}
	//如果重载为Date的成员函数,第一个参数为隐藏的this指针,但cout是ostream类的对象,第一个参数应该是ostream类型,互相矛盾
	// ostream& operator<<(const Date& d);   
private:
	int _year;
	int _month;
	int _day;
};

//为了让第一个参数类型为ostream,故当做全局函数重载
const ostream& operator<<(const ostream& out, const Date& d)
{
	out << d._year << "年" << d.month << "月" << d.day << "日";
	return out;
}

int main()
{
	Date d;
	cout << d;  //重载流插入运算符使其可以输出日期类 
	return 0;
}

        那么问题就来了,既然不能声明为成员函数,那我们在全局函数中要怎么访问Date的私有成员呢?

         这时候就不得不使用我们上面说的友元了,将operator<<声明为Date类的友元函数后,代码成功运行:

class Date
{
public:
	Date(int year = 2023, int month = 1, int day = 1)
		:_year(year)
		, _month(month)
		, _day(day)
	{}
	friend ostream& operator<<(ostream& out, const Date& d); //将operator<<全局函数声明为Date类的友元函数
private:
	int _year;
	int _month;
	int _day;
};

注意事项

  • 友元函数可以访问类中的私有成员,它是定义在类外部的普通函数,但需要在类的内部进行声明,声明时需要加friend关键字
  • 友元函数不能用const修饰,const只能修饰成员函数
  • 友元函数可以在类定义的任何地方声明,不受类访问限定符限制
  • 一个函数可以是多个类的友元函数

5.3 友元类

        友元类中的所有成员函数都可以访问另一个类的非公有成员。友元关系是单向的,不具有交换性。例如B是A的友元类,B中的所有成员函数可以访问A中的私有成员,但A中的成员函数不能访问B中的私有成员。举例如下:

class A
{
	friend class B; //定义B是A的友元类

	void GetSum(B& b)
	{
		cout << b.sum << endl;  //这里会报错,A类的成员函数无法访问B类的私有成员,不具有交换性
	}
private:
	int count = 20;
};

class B
{
	void GetCount(A& a)
	{
		cout << a.count << endl; //通过编译,B是A的友元类,B中成员函数可以访问A的私有成员
	}
private:
	int sum = 10;
};

        以上程序编译时会报错如下


        友元关系也不具有传递性。例如:C是B的友元类,B是A的友元类,无法说明C是A的友元。举例如下

class A
{
	friend class B; //定义B是A的友元类
private:
	int a_sum = 10;
};

class B
{
	friend class C; //定义C是B的友元类
private:
	int b_sum = 20;
};

class C
{
	void GetBSum(B& b)
	{
		cout << b.b_sum << endl;  //编译通过,C是B的友元类
	}
	void GetASum(A& a)
	{
		cout << a.a_sum << endl;  //这里编译器会报错,C不是A的友元类,无法访问私有成员,友元关系不具有传递性
	}
private:
	int c_sum = 30;
};

         以上程序编译时会报错如下 


六. 内部类

        一个类不仅可以定义在全局范围内,还可以定义在另一个类的内部。我们将定义在某个类内部的类称之为内部类。下面的B类就是一个内部类:

class A //A称为外部类
{
public:

	class B //B类在A类的内部定义,称之为内部类
	{
	private:
		int sum; //b类的成员变量
	};

private:
	int count; //a类的成员变量
};

        内部类是一个独立的类,它不属于外部类,更不能通过外部类的对象去访问内部类的成员。外部类对内部类没有任何特殊的访问权限。有以下两个具体体现

  • 内部类可以定义在外部类的的任何位置,不受外部类访问限定符的限制。
  • sizeof(外部类)=外部类,和内部类没有任何关系

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

class A //A是外部类
{
public:

	class B //B是内部类
	{
		int GetACount(A& a)  
		{
			return a.count; //可以访问外部类的私有成员
		}
	private:
		int sum; 
	};

	int GetBSum(B& b)
	{
		return b.sum; //这里会报错,外部类不能访问内部类的私有成员
	}
private:
	int count; //a类的成员变量
};

        内部类可以直接访问外部类中的static成员,不需要外部类的对象/类名,如下所示

class A //A是外部类
{
public:

	class B //B是内部类
	{
		int GetACount()  
		{
			return _count; //可以直接访问外部类的静态成员变量,无需类名/类对象
		}
	private:
		int sum; 
	};

private:
	static int _count; // A中的静态成员变量
};

int A::_count = 10; //类外进行初始化

七. 匿名对象

        C++支持我们不给对象起名字,这样的对象我们称为匿名对象,其定义方式如下:

int main()
{
	//对象类型+():创建一个匿名对象
	A();  //这里就是创建一个匿名对象A
	return 0;
}

        匿名对象的声明周期只在当前行,当前行结束后会自动调用析构函数进行销毁:

       匿名对象具有常属性,即不能对匿名对象中的成员变量进行修改:

int main()
{
	A().count = 10; //编译器报错:表达式必须是可修改的左值
	return 0;
}

        可以给匿名对象取别名,这样可以延长匿名对象的声明周期:

int main()
{
	//给匿名对象取别名
	const A& cla1 = A(); //注意:这里必须是const引用,因为匿名对象具有常性,权限不能放大
	cout << "程序即将结束" << endl;
	return 0;
}

        匿名对象经常用在仅需调用某个类的成员函数的情况,可以简化我们代码的编写。举例如下 

class Solution //Solution类用来求两数之和
{
public:
	int Sum_Solution(int x,int y)  //返回两数之和 
	{
		return x + y;
	}
};

int main()
{
	//不使用匿名对象
	Solution s1; //要先定义一个类对象,这个对象仅仅只是用来调用方法
	s1.Sum_Solution(2, 2); //然后再去调用成员函数

	//使用匿名对象
	Solution().Sum_Solution(2, 3); //代码更加简便
}

        上面的Solution类是不是很熟悉?没错,在我们使用C++进行刷题时每次能够遇到它


 以上,就是本期的全部内容啦🌸

制作不易,能否点个赞再走呢🙏

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

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

相关文章

Java编程题(完数)

题目 一个正整数的因子是所有可以整除它的正整数。而一个数如果恰好等于除它本身外的因子之和&#xff0c;这个数就称为完数。例如61&#xff0b;2&#xff0b;3(6的因子是1,2,3)。 现在&#xff0c;你要写一个程序&#xff0c;读入两个正整数n和m&#xff08;1<n<m<…

《Spring安全配置》

&#x1f337;&#x1f341; 博主猫头虎&#xff08;&#x1f405;&#x1f43e;&#xff09;带您 Go to New World✨&#x1f341; &#x1f984; 博客首页——&#x1f405;&#x1f43e;猫头虎的博客&#x1f390; &#x1f433; 《面试题大全专栏》 &#x1f995; 文章图文…

【Python】简记操作:Centos安装、卸载、升级Python运行环境

目录 安装 1、选择合适自己的python版本 2、选择合适的目录进行指定版本源码下载 3、解压编译安装 解压 编译安装&#xff0c;完成即可执行python相关命令 测试是否已成功安装python 4、设置python的全局环境变量&#xff08;/etc/profile&#xff09; 设置环境变量 校…

RPC分布式网络通信框架项目

文章目录 对比单机聊天服务器、集群聊天服务器以及分布式聊天服务器RPC通信原理使用Protobuf做数据的序列化&#xff0c;相比较于json&#xff0c;有哪些优点&#xff1f;环境配置使用项目代码工程目录vscode远程开发Linux项目muduo网络库编程示例CMake构建项目集成编译环境Lin…

【RabbitMQ 实战】08 集群原理剖析

上一节&#xff0c;我们用docker-compose搭建了一个RabbitMQ集群&#xff0c;这一节我们来分析一下集群的原理 一、基础概念 1.1 元数据 前面我们有介绍到 RabbitMQ 内部有各种基础构件&#xff0c;包括队列、交换器、绑定、虚拟主机等&#xff0c;他们组成了 AMQP 协议消息…

次方计数的拆贡献法(考虑组合意义)+限定类问题善用值域与位置进行ds:1006T3

对于多次方的计数问题可以考虑拆贡献。 题目问 ∣ S ∣ 3 |S|^3 ∣S∣3&#xff0c; ∣ S ∣ |S| ∣S∣ 表示选的点数。相当于在 ∣ S ∣ |S| ∣S∣ 中选了3次&#xff0c;也就是选了3个可相同的点。 先考虑3个不相同点的贡献&#xff0c;对应任意3个点&#xff0c;必然会对…

Go Gin Gorm Casbin权限管理实现 - 2. 使用Gorm存储Casbin权限配置以及`增删改查`

文章目录 0. 背景1. 准备工作2. 权限配置以及增删改查2.1 策略和组使用规范2.2 用户以及组关系的增删改查2.2.1 获取所有用户以及关联的角色2.2.2 角色组中添加用户2.2.3 角色组中删除用户 2.3 角色组权限的增删改查2.3.1 获取所有角色组权限2.3.2 创建角色组权限2.3.3 修改角色…

uni-app 经验分享,从入门到离职(实战篇)——模拟从后台获取图片路径数据后授权相册以及保存图片

文章目录 &#x1f4cb;前言⏬关于专栏 &#x1f3af;需求描述&#x1f3af;前置知识点&#x1f9e9;uni.showLoading()&#x1f9e9;uni.authorize()&#x1f9e9;uni.downloadFile()&#x1f9e9;uni.saveImageToPhotosAlbum() &#x1f3af;演示代码&#x1f9e9;关于图片接…

不能一棍子敲死刚诞生不久的USB-C,虽然它有时确实很惹人厌

今年iPhone机型最大的预期之一是从苹果专有的Lightning端口过渡到USB-C标准。一些人担心过渡需要他们更换所有配件&#xff0c;而另一些人&#xff08;包括你&#xff09;则期待着未来能够为iPad、MacBook和iPhone使用一根电缆。 然而&#xff0c;现在新机型已经问世&#xff…

微信小程序使用路由传参和传对象的方法

近期在做微信小程序开发&#xff0c;在页面跳转时&#xff0c;需要携带参数到下一个页面&#xff0c;尤其是将对象传入页面。为了方便重温&#xff0c;特此记录。 路由传字符串参数 原始页面 传递字符串参数比较简单。路由跳转有两种方式&#xff0c;一种是通过navigator组件…

【AI视野·今日CV 计算机视觉论文速览 第262期】Fri, 6 Oct 2023

AI视野今日CS.CV 计算机视觉论文速览 Fri, 6 Oct 2023 Totally 73 papers &#x1f449;上期速览✈更多精彩请移步主页 Daily Computer Vision Papers Improved Baselines with Visual Instruction Tuning Authors Haotian Liu, Chunyuan Li, Yuheng Li, Yong Jae Lee大型多模…

java Spring Boot在配置文件中关闭热部署

之前更大家一起搭建了一个热部署的开发环境 但是 大家要清楚一个情况 我们线上程序运行突然内部发生变化这是不可能的。 所以 他就只会对我们开发环境有效 是否开启 我们可以通过 application配置文件来完成 我这里是yml格式的 参考代码如下 spring:devtools:restart:enabled…

雷达天线、信号链路、大气衰减基本概念

一、 有效各向同性辐射功率 有效各向同性辐射功率是各向同性天线必须辐射的假设功率,以在天线最强波束的方向上提供与实际源天线相同(等效)的信号强度。 二、电磁波传播 图1 从发射机到接收机的链路路径 信号与环境传播模型远离地面的高频和/或链路路径和/或窄天线Line of …

基础算法之——【动态规划之路径问题】1

今天更新动态规划路径问题1&#xff0c;后续会继续更新其他有关动态规划的问题&#xff01;动态规划的路径问题&#xff0c;顾名思义&#xff0c;就是和路径相关的问题。当然&#xff0c;我们是从最简单的找路径开始&#xff01; 动态规划的使用方法&#xff1a; 1.确定状态并…

《视觉 SLAM 十四讲》V2 第 6 讲 非线性优化 【高斯牛顿法、列文伯格-马夸尔特方法 、Ceres 库 和 g2o库 】

文章目录 6.1.2 最小二乘 寻找下降增量 Δ x k \Delta\bm{x}_k Δxk​的 4 种方法6.2.1 一阶和二阶梯度法(最速下降法、牛顿法)6.2.2 高斯牛顿法6.2.3 列文伯格-马夸尔特方法 【阻尼牛顿法】【信赖区域法】 6.3 实践6.3.1 手写高斯牛顿法 【Code】6.3.2 谷歌的优化库 Ceres 【最…

自动驾驶学习笔记(一)——Apollo平台

#Apollo开发者社区# 学习课程的传送门如下&#xff0c;当您也准备学习自动驾驶时&#xff0c;可以和我一同前往&#xff1a; 《自动驾驶新人之旅》免费课程—> 传送门 《2023星火培训【感知专项营】》免费课程—>传送门 文章目录 前言 Apollo框架 开发平台 总结 前…

【TMS320F28374之PWM】

简介 增强型脉宽调制器(ePWM)外设是控制商业和工业设备中许多电力电子系统的关键元件。这些系统包括数字电机控制、开关模式电源控制、不间断电源(UPS)和其他形式的电源转换。ePWM外设还可以执行数模转换(DAC)功能&#xff0c;其中占空比相当于DAC模拟值;它有时被称为电源DAC。…

[机缘参悟-109] :接纳生活中的无完美是一种修行,过度追求完美是一种“我执”,接纳污秽、肮脏、邪恶、小人是一种高度

目录 前言&#xff1a; 一、天地以万物为刍狗&#xff0c;站在大自然的视角没有善恶 1.1 天地以万物为刍狗&#xff0c;在自然面前&#xff0c;一切生命都平等 1.2 食物链是一个闭环系统&#xff0c;无所谓善恶 1.3 在大自然中&#xff0c;人类眼中的“美丽”都诞生于“污…

Python(八十八)函数的参数传递

❤️ 专栏简介&#xff1a;本专栏记录了我个人从零开始学习Python编程的过程。在这个专栏中&#xff0c;我将分享我在学习Python的过程中的学习笔记、学习路线以及各个知识点。 ☀️ 专栏适用人群 &#xff1a;本专栏适用于希望学习Python编程的初学者和有一定编程基础的人。无…

C++设计模式-单件(Singleton)

目录 C设计模式-单件&#xff08;Singleton&#xff09; 一、意图 二、适用性 三、结构 四、参与者 五、代码 C设计模式-单件&#xff08;Singleton&#xff09; 一、意图 保证一个类仅有一个实例&#xff0c;并提供一个访问它的全局访问点。 二、适用性 当类只能有一…