“三步走”带你拿下C++类与对象(下)

2024-04-20 17:04
文章标签 c++ 对象 拿下 三步走

本文主要是介绍“三步走”带你拿下C++类与对象(下),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

在学习了“上”篇和“中”篇后,我们对类和对象以及一些析构函数有了一定的理解,本文我们将继续深入讲解有关的其他内容。

一、初始化列表的引入

我们以之前的队列为例子(创建两个队列一个用于入栈一个用于出栈)

这个myqueue对内置类型不做处理,对自定义类型会调用其相应的构造函数,这是我们根据之前的知识得来的结论,但现在如果我们的栈不提供默认构造呢?(声明一下:在栈中,我们之前写的显示构造函数是全缺省的开辟空间的函数)此时,myqueue也无法生成其对应的构造,为了使程序正常运行,办法就是在myqueue下的类显示地去写构造,也就引入了初始化列表。

初始化列表的格式:冒号开始,逗号分割,写在构造函数的下方,如图所示

这里作者的理解是,因为stack的类已经没有默认构造函数了,它的同名函数可能是一个需要传参才能进行初始化的函数。此时需要我们写一个queue的构造函数,通过走这个函数来进行对stack类的初始化。注意,下面的{}不可省略。这样,内置类型_size就初始化为0,而作为自定义类型pushst和popst此时就可以调用他们自己创建的类之下的构造函数了(因为已经传参n了),当然,这个queue处写缺省值也是可以的。

初始化列表和函数体内初始化也可以混用,比如上面也可以这么写

初始化列表的本质可以理解为每个对象中成员定义的地方。(这里再强调一下,在queue这个类中,private中的成员只是声明,并没有开空间,而在初始化列表的地方就是将声明的成员进行定义,也就是开空间)

然而,有些对象必须在初始化列表进行初始化,在函数体内初始化就会报错。

比如1.const成员对象

这是因为,const变量必须在定义时被初始化,而我们刚才说初始化列表是成员定义的地方,所以要在上方初始化而不是函数体内部。

2.引用的对象

为了便于引用的初始化,我们增加一个形参。 

3.没有默认构造自定义类型成员(必须显示传参调构造)

——————————————————————————

初始化列表,不管我们写不写,每个对象都会走一遍,对于自定义类型就会去调用默认构造

即使我的初始化列表出什么也没写他也会将每个对象走一遍(此时stack类已经有默认构造函数才没有报错)

我们来看下面的一端代码

首先A类进行初始化,走初始化列表,由于main函数中传了参数所以_a1被初始化为1,然后_a1又作为参数进行_a2的初始化,按道理来讲运行结果应该是1 1,但结果确实1和随机值,为什么呢?关于初始化列表最后一个特点:成员变量在类中声明次序就是其在初始化列表中的初始化顺序,与其在初始化列表中的先后次序无关。也就是说,在这个程序中,因为a2先声明,我们在走初始化列表的时候先走a2的初始化,由于此时a1是随机值所以a2的初始化为随机值,然后a1被初始化为1。至此,初始化列表的相关知识基本就这么多。接下来我们来看一个有意思的东西。

第一行是进行初始化,第二行是我们之前讲过的拷贝构造,可是第三行呢?为什么它不会报错。其实背后的猫腻是——隐式类型转换。也就是说,1并没有直接赋值给aa3,而是先构造一个A的临时对象,在用这个临时对象拷贝构造aa3,在这里进行了优化,也就是连续构造加拷贝构造优化为了直接构造。这个与我们之前学过的内置类型就对得上了。那再看看下面这个

56行的引用赋值为什么就会报错呢?首先这里也肯定有构造,也就是2创建一个A类型的临时对象,但不同的是这里面就没有拷贝构造了。也就是说aa4引用的是类型转换中用2构造的临时对象。但报错的原因其实是权限的放大,所以我们在前面加const即可。

这个东西到底有啥用呢?我们引入一个实例,栈的压栈操作。

这是正常写法,但不足的是,它首先要构造,然后作为参数传参又要拷贝构造造成浪费。此时,我们上面的操作就起作用了。

209行代码就可以平替207+208行,这样就直接实现相同的压栈操作了(2的隐式类型转换给了push函数,且注意要加const否则会报错,因为会造成权限的放大)那多参数的怎么搞呢?这里是这样规定的

同理,237+238行与240的压栈操作结果相同。

二、explicit关键字

如果我们不想进行隐式类型转换,把构造函数用explicit关键字修饰即可。

三、静态成员变量与静态成员函数

声明为static的类成员称为类的静态成员,用static修饰的成员变量,称之为静态成员变量

如果我们去计算这个类的大小会发现是8而不是12,因为静态成员变量在静态区存储,类中不存储成员函数,不存储静态成员变量。此时我们不能在此处给缺省值,因为这个类走初始化列表的时候是不对静态成员变量进行初始化的。应该在全局进行定义。我们想访问的话,用以下两种办法均可

注意,此时是对静态成员变量进行了public处理,如果放在private中就会无法访问,但也有静态成员变量即是private同时我们也能访问的办法,用静态成员函数。

用static修饰的 成员函数,称之为静态成员函数。

注意,在静态成员函数中,我们无法访问类中其他对象的成员,只能访问静态成员,因为在静态成员函数中是没有this指针的。

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

四、友元

1.我们在(中)篇已经讲解了它的语法和用途,在此就简单再回顾一下即可,我们在实现日期类的时候,为了输出类中的内容,实现<<和>>运算符重载函数,发现为了符合我们所认知的cout和cin用法,这两个函数只能写在类域的外部,这就造成了无法访问类中成员的问题,为了解决我们引入了友元。

2.友元类

我们有时候创建两个类的时候,会想在其中一个类中去访问另一个类的成员,显然这是不可以的,但我们把它写成友元类即可,它的写法和友元函数相同 friend class 类名,可以写在想访问的类的任意位置。

这个地方声明的位置不要颠倒,这里是date类可以访问time类的成员,而time类仍无法访问date类 的成员。

五、内部类

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

看,我们在定义B类的变量时不可以直接去像A类那样写,就是因为类域的限制。但如果计算A类的大小,B类中的成员变量并不算入其中,这也进一步说明内部类和外部类的独立性。

但是B天生就是A的友元,这也是内部类的一个特点。内部类随便访问外部类,但外部类不可访问内部类。

六、匿名对象(了解即可)

假设我们随便写一个类,然后创建一个变量,但有时候我们创建的变量没有名字,就叫匿名对象,我们来感受一下他们的差别

35行就是匿名,37行就是我们平常所定义的变量。那么他们在功能上有什么差别呢?

匿名对象的生命周期只在当前这一行,也就是说代码走到35行时开始初始化,构造,一旦走到36行它就调析构函数。而且,我们用匿名对象去调用成员函数也可以在一行实现

————

此系列到此就完结撒花了,希望朋友们多多点赞,后续继续更新高质量文章。

这篇关于“三步走”带你拿下C++类与对象(下)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/920832

相关文章

【C++ Primer Plus习题】13.4

大家好,这里是国中之林! ❥前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站。有兴趣的可以点点进去看看← 问题: 解答: main.cpp #include <iostream>#include "port.h"int main() {Port p1;Port p2("Abc", "Bcc", 30);std::cout <<

C++包装器

包装器 在 C++ 中,“包装器”通常指的是一种设计模式或编程技巧,用于封装其他代码或对象,使其更易于使用、管理或扩展。包装器的概念在编程中非常普遍,可以用于函数、类、库等多个方面。下面是几个常见的 “包装器” 类型: 1. 函数包装器 函数包装器用于封装一个或多个函数,使其接口更统一或更便于调用。例如,std::function 是一个通用的函数包装器,它可以存储任意可调用对象(函数、函数

C++11第三弹:lambda表达式 | 新的类功能 | 模板的可变参数

🌈个人主页: 南桥几晴秋 🌈C++专栏: 南桥谈C++ 🌈C语言专栏: C语言学习系列 🌈Linux学习专栏: 南桥谈Linux 🌈数据结构学习专栏: 数据结构杂谈 🌈数据库学习专栏: 南桥谈MySQL 🌈Qt学习专栏: 南桥谈Qt 🌈菜鸡代码练习: 练习随想记录 🌈git学习: 南桥谈Git 🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈�

【C++】_list常用方法解析及模拟实现

相信自己的力量,只要对自己始终保持信心,尽自己最大努力去完成任何事,就算事情最终结果是失败了,努力了也不留遗憾。💓💓💓 目录   ✨说在前面 🍋知识点一:什么是list? •🌰1.list的定义 •🌰2.list的基本特性 •🌰3.常用接口介绍 🍋知识点二:list常用接口 •🌰1.默认成员函数 🔥构造函数(⭐) 🔥析构函数 •🌰2.list对象

06 C++Lambda表达式

lambda表达式的定义 没有显式模版形参的lambda表达式 [捕获] 前属性 (形参列表) 说明符 异常 后属性 尾随类型 约束 {函数体} 有显式模版形参的lambda表达式 [捕获] <模版形参> 模版约束 前属性 (形参列表) 说明符 异常 后属性 尾随类型 约束 {函数体} 含义 捕获:包含零个或者多个捕获符的逗号分隔列表 模板形参:用于泛型lambda提供个模板形参的名

6.1.数据结构-c/c++堆详解下篇(堆排序,TopK问题)

上篇:6.1.数据结构-c/c++模拟实现堆上篇(向下,上调整算法,建堆,增删数据)-CSDN博客 本章重点 1.使用堆来完成堆排序 2.使用堆解决TopK问题 目录 一.堆排序 1.1 思路 1.2 代码 1.3 简单测试 二.TopK问题 2.1 思路(求最小): 2.2 C语言代码(手写堆) 2.3 C++代码(使用优先级队列 priority_queue)

【C++高阶】C++类型转换全攻略:深入理解并高效应用

📝个人主页🌹:Eternity._ ⏩收录专栏⏪:C++ “ 登神长阶 ” 🤡往期回顾🤡:C++ 智能指针 🌹🌹期待您的关注 🌹🌹 ❀C++的类型转换 📒1. C语言中的类型转换📚2. C++强制类型转换⛰️static_cast🌞reinterpret_cast⭐const_cast🍁dynamic_cast 📜3. C++强制类型转换的原因📝

C++——stack、queue的实现及deque的介绍

目录 1.stack与queue的实现 1.1stack的实现  1.2 queue的实现 2.重温vector、list、stack、queue的介绍 2.1 STL标准库中stack和queue的底层结构  3.deque的简单介绍 3.1为什么选择deque作为stack和queue的底层默认容器  3.2 STL中对stack与queue的模拟实现 ①stack模拟实现

c++的初始化列表与const成员

初始化列表与const成员 const成员 使用const修饰的类、结构、联合的成员变量,在类对象创建完成前一定要初始化。 不能在构造函数中初始化const成员,因为执行构造函数时,类对象已经创建完成,只有类对象创建完成才能调用成员函数,构造函数虽然特殊但也是成员函数。 在定义const成员时进行初始化,该语法只有在C11语法标准下才支持。 初始化列表 在构造函数小括号后面,主要用于给

2024/9/8 c++ smart

1.通过自己编写的class来实现unique_ptr指针的功能 #include <iostream> using namespace std; template<class T> class unique_ptr { public:         //无参构造函数         unique_ptr();         //有参构造函数         unique_ptr(