C++入门全集(4):类与对象【下】

一、再谈构造函数

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 初始化列表

像这样,被const修饰的成员变量,必须在声明时就进行初始化

虽然C++11允许我们给成员变量一个缺省值,但是C++11以前是怎么做的呢?

这里引入一个新的概念:初始化列表

初始化列表定义在构造函数的函数名和函数体之间,以一个冒号开始,接着是一个用逗号分隔的数据成员列表,每个成员变量后面跟着一个放在括号中的初始值或表达式,例如:

Date(int year, int month, int day): _year(year), _month(month), _day(day)
{}

像这样,才能称之为真正的初始化

但是,如果一个成员变量既给了缺省值,又在初始化列表中显式定义了,那它最后的值如何呢?

通过监视窗口我们可以观察到:

如果一个成员变量既有缺省值又在初始化列表中定义了,那么就按照初始化列表中的值进行初始化

如果一个成员变量有缺省值,但是没在初始化列表中定义,那么就用它的缺省值初始化

如果一个成员变量既没有缺省值又没在初始化列表中定义,那么就给一个随机值

初始化列表有以下几个特点:

(1)每个成员变量在初始化列表中只能出现一次,因为只能初始化一次

(2)类中的以下几个成员变量,必须放在初始化列表中进行初始化

  • 引用类型的成员变量
  • const成员变量
  • 没有默认构造函数的自定义类型成员变量

原因如下: 

对于引用类型的成员变量,我们必须在声明变量时就给出它的引用对象

对于const成员变量前面已经提到过了,也是必须在声明时就初始化

对于没有默认构造函数的自定义类型成员变量,我们在初始化时需要传参

(3)尽量多使用初始化列表去初始化,因为不管你是否使用,对于自定义类型成员变量都一定会先使用初始化列表去初始化

例如:

可以看到,Date类里有一个Time类的成员函数,虽然我们没有在初始化列表中初始化它,它也会使用初始化列表去调用自己的默认构造函数

如果该自定义类型成员变量的构造函数不是无参的或者全缺省的,我们就需要手动将该变量添加至初始化列表中并给出参数。

总之,我们平时写构造函数的时候尽量用初始化列表来初始化成员变量即可

(4)成员变量在类中的声明顺序就是它在初始化列表中的初始化顺序,与其在初始化列表中的先后顺序无关

例如:

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

最后打印的结果是什么呢?

可能很多人认为是 1 1,实际上是:

因为_a2比_a1先声明,所以在初始化列表中也是_a2先初始化

最后,拷贝构造函数因为也是构造函数,所以它也有初始化列表 

1.3 explicit关键字

我们知道,内置类型变量在发生类型转换的时候会生成一个临时的常性变量,例如:

int a = 1;
double b = a;

但是,类型转换不止能发生在内置类型中,内置类型也可以转换成自定义类型,这里就和构造函数扯上关系了。

一个类的构造函数,不仅起到初始化成员变量的作用,对于单个参数或第一个参数无缺省值的半缺省构造函数来说,它还具有类型转换的作用

或许没看懂,那就举一个例子

像这样,对象aa1在创建时正常调用构造函数,aa2又是怎么回事?为什么一个自定义类型能被内置类型初始化?

前面说到,内置类型在发生类型转换的时候会生成一个临时的常性变量,这里也是一样

首先编译器使用1作为参数调用构造函数,创建一个临时变量,再用这个临时变量调用拷贝构造函数对aa2赋值

所以aa1只调用了一次构造函数,而aa2这一行代码调用了一次构造函数和一次拷贝构造函数

这种方式既影响代码可读性,又增加了消耗,有什么办法可以禁止构造函数类型转换呢?

这里引入explicit关键字,在构造函数的前面加上它,即可禁止类型转换了

这是单参数的构造函数,对于第一个参数无缺省值的半缺省构造函数也是同理

只要是只传递一个参数的构造函数,用这种方式都会发生类型转换

另外,对于需要传递多个参数的构造函数,在C++11后也开始支持类型转换了,例如:

如果不想类型转换,用explicit修饰构造函数即可


二、static成员

2.1 概念

static修饰的类成员称为类的静态成员,static修饰的成员变量称为静态成员变量,static修饰的成员函数称为静态成员函数

有一道面试题:实现一个类,计算程序中创建过多少个类对象

可以看到,使用静态成员变量和静态成员函数可以很轻松的解决这个问题

2.2 特性

根据上面的图,我们可以得出以下几点 

(1)静态成员不属于某个对象,而是属于所有对象、属于整个类,存放在静态区

所以我们上面可以直接使用类名和作用域限定符来访问静态成员函数GetCount,当然也可以创建一个对象来访问静态成员函数,但是有点多此一举了

2)静态成员变量必须在类外进行定义和初始化,不需要添加static,在类中声明时才需要加

(3)静态成员函数没有隐式的this指针形参,所以不能访问任何非静态成员

(4)静态成员也是类的成员,受public、protected和private访问限定符的限制

像上面,因为_count是私有的,我们只能用函数来获取它,如果它是公有的,我们也可以直接访问


三、友元

当我们在类外定义了一个函数,想要访问类中的私有成员变量怎么办呢?这里就涉及到友元

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

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

3.1 友元函数

我们知道,cout和流插入运算符可以实现将内置类型打印的效果,那么假设我想将流插入运算符重载,让自定义类型也可以使用呢?

我们试试在类中实现流插入运算符的重载函数

可以看到,实现的重载函数没有起效

这是因为,成员函数的第一个变量是this指针,在重载函数中对应第一个变量的是第一个操作数

所以如果像这样就可以正常运行了

但是这样也太怪了吧,和平时用cout一点也不一样,有没有别的办法呢?

我们只好在类外去实现流插入运算符的重载函数了,但是类外的函数又没办法访问类内的私有成员

像这种必须定义在类外,但是又需要访问类内的私有成员的函数,就需要友元来解决了

友元函数可以直接访问类内的私有成员,需要在类的内部进行声明,声明时需要加friend关键字

需要说明以下几点:

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

3.2 友元类

和友元函数类似,我们也可以在类中声明一个友元类

友元类的所有成员函数都是另一个类的友元函数,都可以访问另一个类中的非公有成员

需要注意以下几点:

  • 友元关系是单向的,不具有交换性

比如上面,Date类是Time类的友元,所以可以直接在Date类中访问Time类的私有成员变量;

但是不代表Time类是Date类的友元,不能在Time类中访问Date类的私有成员变量

  • 友元关系不能传递

例如A是B的友元,B是C的友元,不代表A就是C的友元了

  • 友元关系不能继承

这里在讲到继承后再给大家详细介绍


四、内部类

4.1 概念

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

内部类是一个独立的类,它不属于外部类,我们更不能通过外部类的对象去访问内部类的成员

外部类对内部类没有任何优越的访问权限。

内部类与外部类的关联在于:

(1)内部类受外部类的类域限制

例如我们想创建一个内部类类型的变量,需要用作用域限定符

(2)内部类天生就是外部类的友元,但是外部类不是内部类的友元

4.2 特性

(1)内部类定义在外部类的public、protected和private中都是可以的

(2)内部类可以直接访问外部类中的静态成员,而不需要借助外部类的对象或类名

(3)外部类的大小不包括内部类

例如

可以看到,外部类A的大小并没有包括内部类B,所以可以知道内部类的空间也是独立的


五、匿名对象

有时候我们可能只需要调用一次某个类的成员函数,为此如果特意去创建一个对象的话就太麻烦了

这里就可以用到匿名对象。我们平时创建一个对象可能是这样的:

如果要创建一个匿名对象的话,是这样的:

顾名思义,匿名对象在创建的时候是不用取名字的。

匿名对象的特点在于,它的生命周期只在这一行,一旦程序走到了下一行,就会自动调用析构函数销毁。

假设此时我们要调用一次A类中的Print函数,就可以用匿名对象去调用,而不用特意创建一个对象

对于各种一次性的对象创建,我们都可以使用匿名对象。

完.

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

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

相关文章

SOCKS55代理 VS Http代理,如何选择?

在使用IPFoxy全球代理时&#xff0c;选择 SOCKS55代理还是HTTP代理&#xff1f;IPFoxy代理可以SOCKS55、Http协议自主切换&#xff0c;但要怎么选择&#xff1f;为解决这个问题&#xff0c;得充分了解两种代理的工作原理和配置情况。 在这篇文章中&#xff0c;我们会简要介绍 …

Synchronized 详解(一)

在C程序代码中我们可以利用操作系统提供的互斥锁来实现同步块的互斥访问及线程的阻塞及唤醒等工作。在Java中除了提供Lock API外还在语法层面上提供了synchronized关键字来实现互斥同步原语,本文将对synchronized关键字详细分析。 带着问题去理解Synchronized 提示 请带着这…

10、BossCms代码审计

1、任意文件上传 限制 复现 POST /system/extend/ueditor/php/controller.php?actionuploadfile&encodeutf-8 HTTP/1.1 Host: bosscms.com Content-Length: 761 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome…

为什么要学习三维GIS开发?从技术层面告诉你答案

大家都知道GIS开发属于GIS行业中就业薪资较高的岗位&#xff0c;并且测绘、遥感以及城规等相关专业的毕业生纷纷转行做webgis开发。 那么&#xff0c;今天小编从技术层面探讨一下&#xff0c;为什么建议大家不要仅仅停留在webgis&#xff0c;而要继续往前学习三维GIS开发&…

TSINGSEE青犀AI智能分析网关V4智慧油田安全生产监管方案

一、方案背景 随着科技的不断发展&#xff0c;视频监控技术在油田行业中得到了广泛应用。为了提高油田生产的安全性和效率&#xff0c;建设一套智能视频监控平台保障安全生产显得尤为重要。本方案采用先进的视频分析技术、物联网技术、云计算技术、大数据和人工智能技术&#…

Linux设备模型(十) - bus/device/device_driver/class

四&#xff0c;驱动的注册 1&#xff0c;struct device_driver结构体 /** * struct device_driver - The basic device driver structure * name: Name of the device driver. * bus: The bus which the device of this driver belongs to. * owner: The module own…

JavaWeb Tomcat启动、部署、配置、集成IDEA

web服务器软件 服务器是安装了服务器软件的计算机&#xff0c;在web服务器软件中&#xff0c;可以部署web项目&#xff0c;让用户通过浏览器来访问这些项目。 Web服务器是一个应用程序&#xff08;软件&#xff09;&#xff0c;对HTTP协议的操作进行封装&#xff0c;使得程序…

MATLAB读取txt文本数据及可视化指南

MATLAB读取txt文本数据的说明指南 目录 MATLAB读取txt文本数据的说明指南摘要1. 数据准备2. 读取数据3. 绘制图形4. 小结 摘要 在MATLAB中&#xff0c;读取txt文本格式文件数据是一项基本的操作&#xff0c;特别是在数据分析和可视化方面。本文将介绍如何使用MATLAB读取txt文本…

C++ 基础知识

一. 预备知识 1. C的编程方式 过程性语言 (结构化、自顶向下)、面向对象语言、泛型编程 (创建独立于类型的代码) 2. 创建源代码文件的技巧 扩展名&#xff1a;.cpp 二. 第一个程序 - HelloWorld main() 入口点 返回 int 标准库 iostream std: 标准库的缩写 Statement…

王者荣耀整蛊搭建直播新玩法/obs贴纸配置教程

最近很火的王者荣耀整蛊直播&#xff0c;相信很多玩王者的玩家也想开一个直播&#xff0c;但是看到这种直播娱乐效果很有意思也想搭建一个&#xff0c;这里梦哥给大家出了一期搭建的教程&#xff01; 进阶版视频教程&#xff1a; 这期的教程是进阶版新玩法升级&#xff0c;具体…

Vue3:使用 Composition API 不需要 Pinia

在 Vue.js 开发的动态环境中&#xff0c;在单个组件中处理复杂的业务逻辑可能会导致笨重的文件和维护噩梦。虽然 Pinia 提供集中式状态管理&#xff0c;但仅依赖它来处理复杂的业务逻辑可能会导致代码混乱。本文探讨了使用 Composition API 的替代方法&#xff0c;说明开发人员…

代码随想录算法训练营第八天

344. 反转字符串 方法&#xff1a; 方法一&#xff1a; 直接用reverse函数 注意&#xff1a; 代码&#xff1a; class Solution { public:void reverseString(vector<char>& s) {return reverse(s.begin(), s.end());} };运行结果&#xff1a; 方法&#xff1…

搜索算法(算法竞赛、蓝桥杯)--双向DFS+二分查找

1、B站视频链接&#xff1a;B26 双向DFS 送礼物_哔哩哔哩_bilibili #include <bits/stdc.h> using namespace std; int n,m; int g[46];//存储所有物品的质量 int w[1<<23];//存储所有能凑出来的重量 int ans,cnt;//w的个数是cnt//搜索第u个数&#xff0c;和为s; …

Geeker Admin添加若以分离版本的后台作为后台

添加验证码 下载若依赖前后端分离版本&#xff0c;配置好自己数据库&#xff0c;redis连接地址 登录添加验证码 配置自己的若依后端连接地址 添加验证码请求方法 登录页面登录输入框添加验证码&#xff0c;uuid,调用的验证码刷新方法 注意&#xff1a;这里要用响应式定义验证…

5_怎么看原理图之协议类接口之NAND Flash笔记

NAND Flash原理图&#xff1a; 由NAND Flash的原理图可以看出&#xff0c;做为一个存储芯片&#xff0c;只有I/O引脚&#xff0c;并没有地址引脚&#xff0c;怎么传地址&#xff1f;遵循一定的规范&#xff0c;先通过LDATA把地址传出去&#xff0c;再传数据。具体的需要查看芯片…

HTTP Cookie 你了解多少?

Cookie是什么&#xff1f; 先给大家举个例子&#xff0c;F12 打开浏览器的页面之后&#xff0c;我们能在 Response Headers 的字段里面看到一个header 叫做 Set-Cookie&#xff0c;如下所示 图中包含的 Set-Cookie 为 Set-Cookie:uuid_tt_dd10_20293537580-1709432565344-232…

【李沐论文精读】Resnet精读

论文地址&#xff1a;Deep Residual Learning for Image Recognition 参考&#xff1a;撑起计算机视觉半边天的ResNet【论文精读】、ResNet论文逐段精读【论文精读】、【李沐论文精读系列】 一、导论 深度神经网络的优点&#xff1a;可以加很多层把网络变得特别深&#xff0c;…

力扣周赛387

第一题 代码 package Competition.The387Competitioin;public class Demo1 {public static void main(String[] args) {}public int[] resultArray(int[] nums) {int ans[]new int[nums.length];int arr1[]new int[nums.length];int arr2[]new int[nums.length];if(nums.leng…

Linux系统Docker部署RStudio Server

文章目录 前言1. 安装RStudio Server2. 本地访问3. Linux 安装cpolar4. 配置RStudio server公网访问地址5. 公网远程访问RStudio6. 固定RStudio公网地址 前言 RStudio Server 使你能够在 Linux 服务器上运行你所熟悉和喜爱的 RStudio IDE&#xff0c;并通过 Web 浏览器进行访问…

4. 编写app组件

1. 代码 main.ts // 引入createApp用于创建应用 import {createApp} from "vue"// 引入App根组件 import App from ./App.vue createApp(App).mount(#app) App.vue <!-- vue文件可以写三种标签1. template标签&#xff0c;写html结构2. script 脚本标签&…