C++的魔法世界:类和对象的终章

news2025/2/24 9:19:23

文章目录

  • 一、再探构造函数
  • 二、类型转换
    • 2.1隐式类型转换
    • 2.2内置类型的类型转化
    • 2.3explicit关键字
    • 2.4多参数构造
  • 三、static成员
  • 四、友元
  • 五、内部类
    • 内部类的特性
  • 六、匿名对象

一、再探构造函数

类和对象(中)里介绍的构造函数,使用的是赋值实现成员变量的初始化。而构造函数还有另一种初始化方法,就是使用初始化列表。

格式:

​ 成员变量以冒号开始,逗号分隔成员,成员变量后面跟上一个括号,其中放置一个初始值或表达式,函数体内实现格外的功能。

class Myqueue
{
public:
    Myqueue()
    :_pushst(10)
    ,_popst(10)
    {}
private:
	Stack pushst;
	Stack popst;
}

Myqueue的类编译器自动生成的构造函数调用了Stack的默认构造函数,完成初始化。栈没有默认构造函数,需要使用初始化列表实现自定义类型的默认构造函数。

必须使用初始化列表的类:

​ 没有默认构造的类类型成员变量、被const修饰的成员变量(声明时必须初始化)、被引用的成员变量(声明引用时必须初始化)

  • 自定义类型成员变量,没有默认构造想要调用它的构造需要传参,在函数体内无法实现,那就只能在初始化列表中实现传参。
#include <iostream>
using namespace std;
class Stack
{
public:
	Stack(int n)
		: _a(new int[n])
		, _top(0)
		, _capacity(n)
		{}
private:
	int* _a = new int;
	int _top = 0;
	int _capacity = 0;
};
class Myqueue
{
public:
	Myqueue()
		:pushst(10)
		,popst(10)
	{
		cout << "Myqueue()" << endl;
	}
private:
	Stack pushst;
	Stack popst;
};

栈没有实现默认构造函数,只实现了构造函数,在Myqueue中需要对两个栈类的成员变量进行初始化,需要调用它的默认构造而栈类没有实现,从而采用初始化列表。

在初始化列表中可以对这两个成员传参调用它们的构造函数从而实现初始化,而若是在函数体内实现初始化,是无法调用这两个栈类成员变量的构造函数。

  • 每个成员变量在初始化列表中只能出现一次,语法理解上初始化列表可以认为是每个成员变量定义初始化的地方
    • 由于某些原因,C++规定必须给每一个成员变量找一个定义的地方,比如被const修饰的变量、引用变量它们在定义的时候必须初始化。若是在函数体内实现,每个成员可以出现多次不能确定那块位置是否为成员变量的初始化。
class A
{
public:
	A(int n = 4)
		: _n(new int[n])
		, _a(n)
		, _b(n)
	{}
private:
	int* _n;
	int& _a;
	const int _b;
};

若没有使用初始化列表完成 _a、_b成员变量的初始化,编译时会出现这种报错。

error C2530: “A::_a”: 必须初始化引用 error C2789: “A::_b”: 必须初始化常量限定类型的对象

多次初始化编译器也会提示你,某变量已经初始化了。

  • C++支持在成员变量声明的地方给缺省值,给没有在初始化列表显示初始化的成员变量使用

给缺省值的同时也对成员变量在初始化列表初始化

class B
{
public:
	B(int n = 4)
		: _n(new int[n])
		, _a(n)
		, _b(n)
		, _st(4)
	{}
private:
	int* _n = new int;
	int& _a;
	const int _b = 2024;
	Stack _st = 10;
};

在这里插入图片描述

可以发现,运行结果都是按照初始化列表给定的值完成初始化。在这里插入图片描述

给定缺省值,不在初始化列表初始化,编译器会自动调用缺省值使成员变量在初始化列表完成初始化。

  • 尽量在初始化列表实现成员变量的初,始化,如果咱不写编译器也会将其放在初始化列表初始化。内置类型随机赋值或连续赋值,自定义类型调用它的默认构造

  • 初始化列表按照成员变量声明的顺序进行初始化,所以在初始化列表中最好按照声明顺序进行定义别把顺序打乱了。

二、类型转换

2.1隐式类型转换

造构造函数中不仅可以构造初始化对象,对于单个参数或者第一个参数无缺省值其余参数均有默认值的构造函数还具有类型转换的作用

class Date
{
public:
	Date(int year)
		:_year(year)
		{}
private:
	int _year;
	int _month = 3;
	int _day = 7;
};
int main()
{
	Date d1 = 1;
	return 0;
}

上述代码将一个整形赋值给一个自定义类型,这种逻辑很明显是错误的,但是Date类实现了,单个参数的构造函数,使得整形1在赋值给d1对象时,发生了隐式类型转换,将整形1转换为Date类型,从而进行赋值。

而实际的流程:Date里实现的有通过整形为形参实现的单参数构造函数,尝试使用一个int对象给一个Date对象赋值后,编译器使用int对象调用构造函数生成一个Date类型的临时对象,最后通过临时对象调用拷贝构造函数实现对Date对象的赋值。

  • 构造函数–生成临时对象–调用拷贝构造–完成赋值

对于VS编译器,调用构造函数 + 拷贝构造函数的过程,会被优化为:调用构造函数。这是编译器在效率上的优化提升,不同版本编译器的优化效果和逻辑大不相同,类如DEVC++编译器可能就不会去进行优化。


注意:编译器生成的临时对象具有常性

如下图:前面提过,隐式类型转换实际上通过调用构造函数生成临时对象,而临时对象又去调用拷贝构造完成赋值的过程,这里的临时对象具有常性,下图的**普通d1对象尝试对一个具有常性的对象进行引用,将会导致权限放大,**从而引发编译器报错。

在这里插入图片描述

将d1对象使用const修饰后就不会出现这种问题
在这里插入图片描述

但这又引出了新的问题,临时对象在调用完拷贝构造函数,出了作用域它就会被销毁了,而常性d1对象对临时对象使用了引用,这导致d1对象对一块被销毁引用,是野引用.

2.2内置类型的类型转化

对于内置类型的转换,实际上也存在生成临时对象,然后完成拷贝赋值的过程。

int main()
{
    //类似的还有
    int i = 1;
    double j = i;//ok~没问题隐式类型转换
   
    double& j = i//ok~这就有问题,i生成的临时对象具有常性,j想指向它导致权限放大,编译器报错。
    const double& j = i;//使用const修饰,权限平移。
    return 0;
}

2.3explicit关键字

当不期望,隐式类型转换的发生,可以在构造函数前添加关键字: ecplicit

它的功能:禁止使用隐式类型装换

在这里插入图片描述

2.4多参数构造

以上介绍的都是单参数的类型转换,而多参数的构造实现后,该如何赋值?

class Date
{
public:
	Date(int year, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
private:
	int _year;
	int _month = 3;
	int _day = 7;
};
int main()
{
	Date d1 = 2024,10,1;//奇奇怪怪的赋值方法,从来没有见过,当然编译器也不支持这种写法
	return 0;
}

多参数的类型装换需要使用 {}花括号 Date d1 = {2024, 10, 1};,C++11才开始支持

int main()
{
    Date d1 = {2024, 10, 1};
	return 0;
}

同理若是,多参数的构造函数不期望支持隐式类型转化,使用关键字 explicit修饰即可。

三、static成员

  • 用static修饰的成员变量,称为静态成语变量,静态成员变量一定要在类外面进行初始化
  • 静态成员变量为所有类成员共享,不属于某个具体的对象,不存在类中,存在静态区
class pp
{
private:
	int _z;
	int _y;
	static int _x;
};
int pp::_x = 1;
  • 用static修饰的成员函数,称之为静态成员函数,静态成员函数没有this指针
  • 非静态的成员函数,可以访问任意位置的成员变量和成员函数
    • 静态成员函数,只能访问静态成员变量和静态成员函数,它没有this指针
class pp
{
public:
	pp(int z = 3, int y = 2)
		:_z(z)
		,_y(y)
	{}
	static int Get_x()
	{
		_z++;
		return _x;
	}
    void Fun()
	{
		_x++;
		_y++;
	}
private:
	int _z;
	int _y;
	static int _x;
};
int pp::_x = 1;

类如上述静态成员函数,它被static修饰后就没有this指针,在 Get_x 函数内调用就回引起编译器报错。 error C2597: 对非静态成员“pp::_z”的非法引用

Fun函数就不会有什么问题,它并不是静态成员函数,函数内部使用this指针,和静态成员变量都是正确的。

  • 静态成员也受 private、public、protected访问限定符的限制

  • 突破类域就可以访问静态成员变量,可以通过 类名::静态成员 或者 对象.静态成员来访问静态成员变量、函数。

    class pp
    {
    public:
    	pp(int n = 1)
    	{
    		_z = n;
    		_y = n;
    	}
    private:
    	int _z;
    	int _y;
    public:
    	static int _x;
    };
    int pp::_x = 10;
    int main()
    {
    	pp p;
    	cout << p._x << endl;
    	cout << pp::_x << endl;
    	return 0;
    }
    

若静态成员变量收限定符的限制,无法通过类名或对象类进行访问,此时可以写一个get函数来获取静态成员函数的大小。

  • 静态成员变量不能在声明位置给缺省值初始化,这个缺省值是构造函数初始化列表使用的,而静态成员变量不属于某个对象,不能走构造函数初始化列表。
class A
{
private:
    static int _count;
}
int A::_count = 1;

int main()
{
    A a1;
    A a2;
    A a3;//a1、a2、a3都可以访问
    cout << A::_count <<endl;
    cout << a1._count << endl;//没有被private修饰,两种突破类域的方法
    
    a1.GetCount();//或者在类中实现获取count的函数。
    return 0;
}

四、友元

友元,是一种用于突破类访问限定符封装的方式,通过它就可以使外部函数访问类的私有和包含成员。而友元分为:友元函数、友元类。在函数声明或类声明的位置前面加friend,并将其放在一个类中。

  • 友元函数/类可以访问类的私有和保护成员,友元函数仅仅是一种声明,他不是类的成员。
class Date
{
	friend ostream& operator<<(ostream& out, const Date d);
public:
	Date(int year, int month, int day)
	{
		_year = year;
		_month = month;
		_day = day;
    }
private:
	int _year;
	int _month;
	int _day;
};
ostream& operator<<(ostream& out, const Date d)
{
	cout << d._year << "年" << d._month << "月" << d._day << "日" << endl;
}
  • 友元函数/类可以在类定义的任何地方使用,不受访问限定符限制
  • 一个函数可以是多个类的友元
#include <iostream>
using namespace std;


class hh;//前置声明
         //若不做声明,pp中的友元函数声明不认识hh类
class pp
{
	friend void Fun(const pp& p, const hh& h);
public:
	pp(int n = 2)
		: _a(n)
		, _b(n)
	{}

private:
	int _a;
	int _b;
};
class hh
{
	friend void Fun(const pp& p, const hh& h);
public:
	hh(int n = 5)
		: _m(n)
		, _n(n)
	{}

private:
	int _m;
	int _n;
};
void Fun(const pp& p, const hh& h)
{
	cout << h._m << endl;
	cout << p._a << endl;
}

  • 友元类的关系是单向的,A类是B类的友元,B类不是A类的友元。

    • 就好比张三和李四是好盆友,李四和王五是好朋友,但这并不代表王五是张三的好朋友,他们两个认不认识还不一定
  • 友元类的关系不能传递,A类是B类的友元,B类是C类的友元,但A类不是C类的友元。

    • 类似友人B找了两个女朋友,分别是友人A、友人C,这时候友人B脚踏两只船。

      而友人B可不希望两个女朋认识,友人B都给女朋友说我只爱你一个人,然而呢~,某一天友人B和友人A出去逛商场的时候恰巧碰见了友人C,诶哟我去!这个场面给位自己想象吧 ~,嘿嘿

  • 友元的存在增加了耦合度,破坏了封装,不宜多用。

五、内部类

如果一个类定义在另一个内的内部,那就将这个类称之为内部类

概念:

内部类是一个独立的类。内部类与定义在全局相比,内部类受到了外部类类域限制和访问限定符的限制,外部类定义的对象中不包含内部类

下列,在类M中定义了一个N类,那这个N类就是内部类

class M
{
public:
	M(int n = 3)
		:_a(n)
		,_b(n)
	{}
private:
	int _a;
	int _b;

public:
	class N
	{
	public:
		void Fun(const M& m)
		{
			cout << m._a << endl;
		}
    private:
        int _x;
	};
};
int main()
{
    M m;
	cout << sizeof(m) << endl;
    return 0;
}
  • 内部类天生就是外部类的友元,内部类可以通过外部类的对象参数来访问外部类中所有成员,但外部类不是内部类的友元

  • N类包在M类里面,若需要单独实例化N类,将会收到外部类类域的限制,此时需要使用域作用限定符 ::
    在这里插入图片描述

内部类的特性

  • 内部类定义在外部类的public、private、protected都是可以的
class pp
{
public:
	class A
	{
	private:
		int _a;
	};
private:
	class B
	{
	private:
		int _b;
	};
protected:
	class C
	{
	private:
		int _c;
	};
};
  • 由于内部类是外部类的友元,内部类在访问外部类的不需要加 类名/对象名

  • 内部类并不会占用外部类的空间容量,使用sizeof计算外部类的大小不会与内部类有关

  • 若两个类的关系紧密相连,B类的功能主要提供给A类使用,就可以考虑将B类实现为A类的内部类。若给内部类使用访问限定符修饰(private/protected),那这个内部类就是外部类的专属,其余类无法使用。

六、匿名对象

匿名对象,没有类名的对象。语法:类名()

可以发现,在下列代码中定义了两个对象:一个有名字,一个没有名字。没有名字的对象就称为匿名对象。

#include <iostream>
using namespace std;
class C
{
public:
	C(int n = 10)
	{
		cout << "调用构造" << endl;
		_c = n;
	}
	~C()
	{
		cout << "调用析构" << endl;
	}
	int ret() { return _c; }
private:
	int _c;
};
int main()
{
	C c1(66);
	C(6);
	return 0;
}

首先介绍匿名对象特别重要的一个特性:匿名对象的声明周期只有当前行,当编译器读到下一行代码时,匿名对象就会被自动销毁了。

在这里插入图片描述

  • 没有对象名的匿名对象也可以调用成员函数

在这里插入图片描述

  • 匿名对象的引用

    • 匿名对象具有常性,被引用需要将const修饰,这样做它的生命周期被延长了,不会即用即销毁,在程序运行结束后才会销毁。const A& r = A(); 但它被引用后而匿名对象的空间被销毁,导致野引用,这在语法上是不允许的
    • 反之,没有使用const进行修饰,在编译时会引起编译器的报错

在这里插入图片描述

匿名对象作函数缺省值:

class A
{
public:
	A(int n = 2)
        :_a(n)
    {}
    ~A()
    {
        cout << "~A()" << endl;
    }
private:
    int _a;
};
void fun(A aa = A(1))//函数缺省值,给匿名对象
{
    
}
int main()
{
    A();//这是一个匿名对象。没有参数也必须加上括号
    A(10);//这是一个匿名对象。
    return 0;
}

在这里插入图片描述

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

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

相关文章

【word】文章里的表格边框是双杠

日常小伙伴们遇到word里插入的表格&#xff0c;边框是双杠的&#xff0c;直接在边框和底纹里修改边框的样式就可以&#xff0c;但我今天遇到的这个有点特殊&#xff0c;先看看表格在word里的样式是怎么样&#xff0c;然后我们聊聊如何解决。 这个双杠不是边框和底纹的设置原因…

亚洲 Web3 市场:Q3 监管变化与市场驱动力探析

概述&#xff1a; 亚洲的 Web3 市场在2024年第三季度继续表现出强劲增长势头。得益于技术精通的人口基础、政府的积极政策导向和企业的大规模参与&#xff0c;韩国、日本、越南等国家已然走在行业前沿。此外&#xff0c;随着越来越多的监管框架落地&#xff0c;区块链创新不断…

Ubuntu20.04下安装多CUDA版本,以及后续切换卸载

本方案的前提是假设机子上已经有一个版本的cuda&#xff0c;现在需要支持新的torch2.1.2和torchvision0.16.2&#xff0c;于是来安装新的cuda 一、选择版本 如果我想安装支持torch2.1.2的cuda版本&#xff0c;到官网&#xff08;https://pytorch.org/get-started/previous-ve…

【Python文件操作】掌握文件读写和目录管理的技巧!

【Python文件操作】掌握文件读写和目录管理的技巧&#xff01; 在现代编程中&#xff0c;文件操作是不可避免的一部分&#xff0c;尤其是在处理数据、日志、配置文件等场景下。Python 提供了强大而简洁的文件操作方法&#xff0c;可以轻松完成文件的读取、写入和目录管理等操作…

005_django基于Python的乡村居民信息管理系统设计与实现2024_106f2qg9

目录 系统展示 开发背景 代码实现 项目案例 获取源码 博主介绍&#xff1a;CodeMentor毕业设计领航者、全网关注者30W群落&#xff0c;InfoQ特邀专栏作家、技术博客领航者、InfoQ新星培育计划导师、Web开发领域杰出贡献者&#xff0c;博客领航之星、开发者头条/腾讯云/AW…

SpringMVC源码-异常处理机制

定义一个异常处理类TestErrorController: Controller public class TestErrorController {RequestMapping("/exception")public ModelAndView exception(ModelAndView view) throws ClassNotFoundException {view.setViewName("index");throw new ClassNot…

Mysql主从集群搭建+分库分表+ShardingSphere(实战)

什么是 ShardingSphere 介绍 Apache ShardingSphere 是一款分布式的数据库生态系统&#xff0c; 可以将任意数据库转换为分布式数据库&#xff0c;并通过数据分片、弹性伸缩、加密等能力对原有数据库进行增强。 Apache ShardingSphere 设计哲学为 Database Plus&#xff0c;…

CRMEB标准版Mysql修改sql_mode

数据库配置 1.宝塔控制面板-软件商店-MySql-设置 2.点击配置修改&#xff0c;查找sql-mode或sql_mode &#xff08;可使用CtrlF快捷查找&#xff09; 3.复制 NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION 然后替换粘贴&#xff0c;保存 注&#xff1a;MySQL8.0版本的 第三步用…

Redis --- 第四讲 --- 常用数据结构 --- string类型

一、认识数据类型和编码方式 有序集合&#xff0c;相当于除了存储member之外&#xff0c;还需要存储一个score&#xff08;权重&#xff0c;分数&#xff09; Redis底层在实现上述数据结构的时候&#xff0c;会在源码层面&#xff0c;针对上述实现进行特定的优化&#xff0c;来…

文生图:Stable Diffusion、Midjourny

前言 Stable Diffusion&#xff08;SD&#xff09;和Midjourney&#xff08;MJ&#xff09;是当前流行的两款AI图像生成工具&#xff0c;它们各有特点和优势&#xff1a; **- Stable Diffusion是完全开源的&#xff0c;**这意味着用户可以免费使用&#xff0c;并且有技术能力…

excel如何把年龄转换为日期

总体的思路 我们从一个核心的日期函数出发 我们首先需要年月日 我的数据大概是这样的。 获取年份 第一步&#xff1a;提取岁前面的数字 left(目标单元格&#xff0c;“从左到右获取第几个字符”)第二步:替换掉数字后面的岁 第三步:新增一个单元格 在里面填入年 第四步:用…

Android系統Audio hal

一.Android系統Audio hal简介 Android系统的音频硬件抽象层(HAL)是系统与硬件之间的桥梁,允许音频应用和服务访问底层音频硬件,而无需直接与硬件交互。 主要组件: 音频 HAL 接口:定义了应用和服务如何调用音频硬件的规范。典型的音频操作包括播放、录制、音量控制等。 …

N1060A 50/85GHz精密型波形分析模块

N1060A 50/85GHz精密型波形分析模块 苏州新利通 概述 Keysight N1060A 精密型波形分析仪是一款数字通信分析仪&#xff08;DCA&#xff09;模块&#xff0c;可与 Keysight N1000A 主机兼容。 与是德科技的所有其他 DCA 模块一样&#xff0c;N1060A 提供了广泛的配置和性能选…

【C语言】数组函数冒泡排序bubble sort

数组&#xff1a;对于n个数字进行排序&#xff0c;就必须定义n个变量来存储。那么为了统一处理&#xff0c;选择数组就十分便捷了。 函数&#xff1a;将排序算法写到函数中&#xff0c;后续遇到所有的排序需求&#xff0c;都可以直接进行调用。 冒泡排序&#xff1a;受气泡在水…

HDFS详细分析

目录 一、HDFS架构 &#xff08;1&#xff09;Block - 数据块 &#xff08;2&#xff09;MetaData - 元数据 &#xff08;3&#xff09;NameNode - 主结点 &#xff08;4&#xff09;DataNode - 从结点 &#xff08;5&#xff09;SecondaryNameNode 二、HDFS的特点 &…

【19楼-注册安全分析报告】

前言 由于网站注册入口容易被黑客攻击&#xff0c;存在如下安全问题&#xff1a; 暴力破解密码&#xff0c;造成用户信息泄露短信盗刷的安全问题&#xff0c;影响业务及导致用户投诉带来经济损失&#xff0c;尤其是后付费客户&#xff0c;风险巨大&#xff0c;造成亏损无底洞…

2024年下软考——信息系统运行管理员考前30天冲刺学习指南!!!

2024下半年软考已经迫在眉睫了&#xff0c;还没有开始备考的小伙伴赶紧行动起来。为了帮助大家更好的冲刺学习&#xff0c;特此提供一份信息系统运行管理员考前30天学习指南。本指南包括考情分析、学习规划、冲刺攻略三个部分&#xff0c;可以参考此指南进行最后的复习要领&…

javaWeb项目-Springboot+vue-校园论坛系统功能介绍

本项目源码&#xff08;点击下方链接下载&#xff09;&#xff1a;java-springbootvue-xx学校校园论坛信息系统实现源码(项目源码-说明文档)资源-CSDN文库 项目关键技术 开发工具&#xff1a;IDEA 、Eclipse 编程语言: Java 数据库: MySQL5.7 框架&#xff1a;ssm、Springboot…

鼠标移入盒子,盒子跟随鼠标移动

demo效果&#xff1a; 鼠标移入盒子&#xff0c;按下鼠标,开启移动跟随移动模式,再次按下关闭移动模式 涉及主要属性 在元素上单击鼠标按钮时输出鼠标指针的坐标&#xff1a; var x event.pageX; // 获取水平坐标 var y event.pageY; // 获取垂直坐标元素offsetL…

【含开题报告+文档+PPT+源码】基于SSM的景行天下旅游网站的设计与实现

开题报告 随着互联网的快速发展&#xff0c;旅游业也逐渐进入了数字化时代。作为一个旅游目的地&#xff0c;云浮市意识到了互联网在促进旅游业发展方面的巨大潜力。为了更好地推广云浮的旅游资源&#xff0c;提高旅游服务质量&#xff0c;云浮市决定开发一个专门的旅游网站。…