C++类和对象再探

news/2024/4/29 17:16:57/文章来源:https://blog.csdn.net/weixin_61488314/article/details/130587758

文章目录

  • const成员
  • 再谈构造函数
  • 成员变量的定义
    • 函数体内赋值
    • 初始化列表
  • 隐式类型转换
  • explicit
  • static成员

const成员

我们知道在调用类的成员函数时,会有一个默认的this指针且这个this指针时不可以被修改的,例如在日期类中,会有隐式的Date * const this;注意这里默认会在this前加const让指针的指向不被改变,那么如何让指针指向的成员变量不可以被改变呢?

class Date
{
public:Date(const int year = 2003, const int month = 9, const int day = 19){_year = year;_month = month;_day = day;}void Print(){cout << _year << ' ' << _month << ' ' << _day << endl;}
private:int _year = 1998;int _month = 1;int _day = 1;
};
int main()
{Date d1(2000, 12);const Date d2;d1.Print();//d1.Print(&d1);     Date* const this;//d2.Print();//d2.Print(&d2);		Date const* const this;return  0;
}

为什么d2不可以调用成员函数,而d1可以?
本质原因是因为形参与实参不匹配,对象d2因为被const修饰所以成员变量不允许被改变,而在传过去时默认的this指针指向的成员变量是可以被改变,这就是权限的放大,所以不被允许。

void Print() const{cout << _year << ' ' << _month << ' ' << _day << endl;}

在被调的成员函数后面加上修饰符const,这里的const修饰的是* this,也就由原来默认的
Date
const this变成了Date const* const this;this和*this都不可以被改变。
只要函数内部this不发生改变,都可以在后面加上const对this进行修饰,这样const对象和普通对象都可以调用这个函数

再谈构造函数

前面我们提到默认构造函数,那么是不是每一个类中都要一个默认构造函数呢?当我们不显示的写构造函数时,编译器会默认生成一个无参的默认构造函数。默认构造函数就不是不需要参数就能够调用的,有三种一种就是我们不写构造函数时,编译器自己生成的,另一种是我们自己定义的构造函数,且没有参数,还有一种我们自己写的全缺省的构造函数,这三种都叫做,默认构造函数。而如果我们自己写的构造函数,就是必须手动传参才能够调用的构造函数,这种就不是默认构造函数,如果一个类中没有默认构造函数,那么我们就必须显示的调用我们自己写的那个构造函数,且要满足这个构造函数对参数的要求。
综上,我们可以只写构造函数不写默认构造函数,但是在初始化对象时,就必须要与我们自己写的构造函数的参数对应上,不过我们建议最好要写上,不然有时直接定义对象时,不传参就会出现没有合适的默认构造的情况。

成员变量的定义

首先我们要弄清楚类成员变量是在哪里定义和声明的

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

一个成员变量只能在一个地方定义,并且只能初始化一次,不可以对同一个变量重复定义,但是当这个变量定义好之后,我们可以对它重复赋值,例如

int main()
{int a = 5;a = 6;a = 7;
}

那么上面的日期类中成员变量是在哪里定义和声明的呢?
声明

private:int _year;int _month;int _day = 1;

这里只是对成员变量的声明并不是定义,不占空间,这里的_day = 1,其中1是_day的缺省值,会在初始化列表中起作用,但如果形参day也有缺省值那么它就会被替代

函数体内赋值

Date(const int year, const int month, const int day){_year = year;_month = month;_day = day;}

构造函数大括号内是对成员变量赋值的地方,不是成员变量变量的地方

初始化列表

成员变量的定义是通过初始化列表对成员变量进行定义,并初始化,且有三种情况只能在初始化列表进行初始化。上面已经说过了一个变量只能初始化一次,如果在初始化列表中对成员变量初始化了,那么在大括号中的就是对它的赋值而不是初始化。
只能在初始化列表进行初始化的三种情况:
1.引用成员变量
2.const成员变量
3.自定义类型成员(且该类没有默认构造函数时)
对于内置类型可以在初始化列表进行定义,在大括号内进行初始化
为什么上面三种情况定义和初始化不可以分开呢?
前两种情况因为引用成员变量和const成员变量必须在定义时就对它进行初始化,第三种情况,是因为它是自定义类型,在定义时编译器默认就要对它进行初始化,而此时它却没有默认构造函数(因为你定义了其它普通的构造函数),所以你就必须显式的对你写的构造函数传参,来对这个自定义类型成员变量进行初始化。如果在有默认构造函数的情况下,我们就可以不显式的去对它进行初始化,因为编译器会自动调用默认构造函数对它进行初始化。而内置类型C++不对它进行处理,如果没有缺省值,在你不在初始化列表写的情况下,它默认只在初始化列表定义但不初始化。
总结:所有的成员类变量默认都要走一遍初始化列表,因为这里是成员变量定义的地方,但是有的成员变量必须在初始化列表进行初始化,有的则可以先定义在后面的大括号里进行初始化。那为什么有时候我们不写初始化列表,也可以直接在大括号里面进行赋值呢,比如上面的日期类,那是因为即使我们不显式的写出来,它也会默认的在初始化列表进行定义,并对自定义类型自动调用它的构造函数对它初始化,而另外三种必须在初始化列表进行初始化情况,如果我们选择直接在大括号内进行对它进行赋值的话就会出错,编译器不允许这种情况的存在。
初始化列表位于构造函数小括号和大括号之间,以冒号开头,不同变量之间用逗号分隔,初始化的值放在要初始化变量的括号里面

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

三种必须显式在初始化列表进行初始化的情况

class A
{public:A(int a):_a(a){}private:int _a;
};class B
{public:B(int a, int& ref):_aobj(a),_ref(ref),_n(10){}private:A _aobj; // 没有默认构造函数int& _ref; // 引用const int _n; // const 
};

成员变量声明的顺序就是成员变量在定义时的顺序,与初始化列表中成员变量的顺序无关,所以我们在写成员初始化列表时,最好让初始化列表中变量的顺序与声明时的顺序保持一致,不然可能会出错。初始化列表是定义的地方,所以定义的顺序就是成员变量声明的顺序,如果你在大括号内进行初始化的话,是什么顺序就是什么顺序。

隐式类型转换

在之前我们知道在把一个变量拷贝值给另一个变量值时,其实不是直接拷贝而是会产生一个临时变量,例如一个double类型的变量赋给int类型的变量,这期间就会先产生一个int类型的临时变量,在把int类型的临时变量赋给int类型的变量,并且这个临时变量具有常性,而在赋值期间产生不同类型的临时变量的过程,就是隐式类型的转换,是由编译器主动完成的。
那么问题来了是否可以从一个内置类型转换成自定义类型呢?

//隐式类型转换
class A
{public:A(const int val):_val(val){cout << "A(const int val)" << endl;}private:int _val;
};int main()
{A a1(5);A a2 = 10;//A& a3 = 12;return 0;
}

在a1中是直接调用构造函数,那么像a2的这种写法是否可以呢?它的具体过程又是什么样的呢?
a2的写法是可以的,不过它与a1略有不同,它包含隐式类型转换的过程,先由int类型的10通过调用构造函数,然后产生自定义类型A的临时变量,再由临时变量A拷贝构造给a2,实际上编译器对这种情况也会优化,优化为直接构造。那么你可能会有疑惑,我怎么知道它到底有没有进行隐式类型转换产生临时变量的过程呢?
例如上面a3的这种写法

A& a3 = 12;//错误的

在这里插入图片描述

这种写法是错误的,编译并不会通过,为什么呢?
其实就是因为中间产生了一个具有常性临时变量的原因,而这里的引用a3是变量,权限放大,自然不能绑定到常量上。

const A& a3 = 12;

如果加上了const让引用也变成常量,不可以被改变那么就可以,这里是权限的平移,所以这就很好的证明了这期间确实发生了隐式类型转换,先通过构造函数产生了一个临时变量。
隐式类型转换为自定义类型只适用于只有一个参数的自定义类型,如果有多个参数就不可以

explicit

英文意思为显式,显性
在编写程序的过程中可能需要从一个类型转换成另一个类型这期间可能会发生隐式类型转换
例如显式的

int i = 5;
double d = (int)i;

单参构造函数,没有使用explicit修饰,具有类型转换作用。
为提高代码的可读性我们可以阻止隐式类型转换的发生,那么在转换为自定义类型的过程中我们怎么阻止隐式类型的转换呢?
explicit修饰构造函数,禁止类型转换。

static成员

有时候我们希望一个变量在函数调用后不被销毁,在下一次再调用这个函数时,其中一个变量不用再次被创建,且还保持为上一次的值,为了满足这些条件我们可以定义一个全局变量,但是全局变量也有一个坏处,就是谁都可以访问谁都可以改变,而当我们放在类中声明为静态成员变量时就会安全很多,不会被轻易访问并改变。
与普通成员变量不同的是静态成员变量属于这个类,而普通成员变量属于每一个类的对象

class A
{
public:A(int a, int b = 6):_a(a),_b(b){}static int GetStatic(){return _c;}void Print(){cout << _a << '-' << _b << '-' << _c << endl;}private:int _a = 4;int _b = 3;static int _c;//静态成员变量声明的地方
};int A::_c = 1;//这里是类的静态成员变量定义的地方int main()
{A a1(4, 8);a1.Print();cout << a1.GetStatic() << endl;cout << A::GetStatic() << endl;return 0;
}

初始化列表是每一个对象的成员变量定义的地方,而静态成员变量_c是属于类的而不是属于对象,所以它不可以在初始化列表定义,也不会默认像普通成员变量一样走一遍初始化列表。C++规定类的静态成员变量在定义时可以通过访问限定符或者类域突破访问限定符,从而有一次可以在类外定义的机会,所以静态成员变量的定义可以在类外。类的静态成员变量规定在类外定义。
既然规定静态成员变量只有在定义时才可以通过类域或者访问限定符来访问,那么在定义之后我们要是想访问怎么访问呢?
我们可以再类中再定义一个函数来获取静态变量的值,但是对于访问静态成员变量我们一般定义一个静态成员函数与它对应,静态成员变量和静态成员函数这两个通常来说是配对使用的,因为静态成员变量属于类而不是属于哪一个对象,被存储在类中,而对于普通成员变量来说,是存储在每一个对象中的,因此每个对象的成员变量对应的值可能不同,所以在访问某一个对象的成员变量时就必须指明是哪一个对象的成员变量,对于静态成员变量它是属于这个类的,存储在类中,而不是存储在某一个对象中,它是被所有对象共享的,所以对于所有对象而言这个静态成员变量的值都是一样的,所以我们也可以不通过指明对象来访问。
静态成员函数形参中没有默认的形参this,所以可以直接通过类域来访问
静态成员函数的好处在于它不仅可以通过访问限定符来访问,也可以通过指定类域来访问,而普通成员函数只能通过访问限定符来进行访问,因为普通成员函数包含this指针,所以要给定确定的对象。
因为静态成员函数没有this指针,所以静态函数体内部不可以通过this来访问普通的成员变量,隐式写和显式写this都不行。
在这里插入图片描述
在这里插入图片描述

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

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

相关文章

Flutter仿写微信导航栏快速实现页面导航

文章目录 前言使用TabBar实现TabBar介绍TabBar的重要属性说明TabBarView介绍TabBarView的重要属性TabBar总结TabBar实现底部导航的例子 BottomNavigationBar实现BottomNavigationBar介绍BottomNavigationBar实现底部导航栏的例子 总结BottomNavigationBarTabBar根据实际情况选择…

【Vue基础】Element案例学习-智能学习辅助系统

一、效果展示 初步设计一个系统&#xff0c;有目录、搜索栏、表格操作等。 二、参考代码 主要关注上图“App.vue”和“BtestView.vue”两个文件的代码 1、App.vue <template><div ><!-- <h1>{{ message }}</h1> --><!-- <element-view&…

暴涨700w播放,星穹铁道恰饭频频登上B站爆款热榜!

B站作为现在年轻一代聚集的多元化社区&#xff0c;游戏内容则是社区内受众较为广泛的存在&#xff0c;而星铁作为面向年轻群体的回合制游戏&#xff0c;自然是赢得B站核心用户群体的青睐。 4月26日&#xff0c;暌违已久的手游《崩坏&#xff1a;星穹铁道》&#xff08;后文简称…

JavaEE(系列6) -- 多线程(解决线程不安全系列1-- 加锁(synchronized)与volatile)

首先我们回顾一下上一章节引起线程不安全的原因 本质原因:线程在系统中的调度是无序的/随机的(抢占式执行) 1.抢占式执行 2.多个线程修改同一个变量. 一个线程修改一个变量>安全 多个线程读取同一个变量>安全 多个线程修改不同的变量>安全 3.修改操作,不是原子的.(最…

Python带你实现批量自动点赞小程序

前言 大家早好、午好、晚好吖 ❤ ~欢迎光临本文章 所用知识点: 动态数据抓包 requests发送请求 json数据解析 开发环境: python 3.8 运行代码 pycharm 2022.3 辅助敲代码 requests 请求模块 &#xff0c;第三方&#xff0c;需安装 win R 输入cmd 输入安装命令 pip inst…

初步认识性能测试和完成一次完整的性能测试

上一篇博文主要通过两个例子让测试新手了解一下测试思想&#xff0c;和在做测试之前应该了解人几点&#xff0c;那么我们在如何完成一次完整的性能测试呢&#xff1f; 测试报告是一次完整性能测试的体现&#xff0c;所以&#xff0c;这里我给出一个完整的性能测试报告&#xff…

springBoot中使用redis实现分布式锁实例demo

首先 RedisLockUtils工具类 package com.example.demo.utils;import org.junit.platform.commons.util.StringUtils; import org.springframework.context.annotation.Bean; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.red…

SAP入门到放弃系列之需求管理的基本要素

需求管理目标&#xff1a; 一般而言&#xff0c;生产计划&#xff08;PP&#xff09;的总体目标&#xff0c;特别是需求管理的总体目标是通过减少以下内容来更好地为客户服务&#xff1a; 补货提前期存货成本 需求管理的要素&#xff1a; 需求管理工作的主要要素广义上可分…

❤ cannot read properties of null(reading appendChild)解决办法

❤ 操作元素报&#xff1a;cannot read properties of null(reading appendChild)解决办法 1、场景&#xff1a; 写的一个js渲染&#xff0c;但是出了个小问题&#xff0c;cannot read properties of null(reading appendChild)报错。 <div id"divps" class&qu…

机器学习项目实战-能源利用率 Part-1(数据清洗)

1. 项目背景 2009年的《当地法案84号》&#xff0c;或纽约市基准法案&#xff0c;要求对能源和用水量进行年度基准测试和披露信息。被覆盖的财产包括单个建筑物的税收地块&#xff0c;其总建筑面积大于50,000平方英尺&#xff08;平方英尺&#xff09;&#xff0c;以及具有超过…

OpenAI新作Shap-e算法使用教程

一、知识点 Shap-e是基于nerf的开源生成3d模型方案。它是由如今热火朝天的Open AI公司&#xff08;chatgpt&#xff0c;Dell-E2&#xff09;开发、开源的。Shap-e生成的速度非常快&#xff0c;输入关键词即可生成简单模型&#xff08;限于简单单体模型&#xff09;。 二、环境…

别去外包,干了三年,废了....

先说一下自己的情况&#xff0c;大专生&#xff0c;18年通过校招进入湖南某软件公司&#xff0c;干了接近4年的功能测试&#xff0c;今年年初&#xff0c;感觉自己不能够在这样下去了&#xff0c;长时间呆在一个舒适的环境会让一个人堕落!而我已经在一个企业干了四年的功能测试…

jetson nx 用windows远程连接

VNC Viewer远程连接 一、jetson nx配置vnc 1、安装客户端 sudo apt-get install xrdp vnc4server xbase-clients2、进入nano/nx桌面&#xff0c;打开“Setting–>Desktop sharing”&#xff0c;没反应&#xff0c;据说是bug&#xff0c;我试过nano和nx都一样。首先输入下…

springboot+jsp法律知识分享网站普法平台

法律知识分享平台&#xff0c;主要的模块包括查看主页、个人中心、用户管理、律师事务所管理、律师管理、法律资讯管理、案例分析管理、案例分享管理、法规信息管理、法规分享管理、留言信息管理、留言回复管理、论坛管理、系统管理等功能。系统中管理员主要是为了安全有效地存…

Docker笔记7 | 如何使用 Docker Compose 搭建一个拥有权限 认证、TLS 的私有仓库?

7 | 如何使用 Docker Compose 搭建一个拥有权限 认证、TLS 的私有仓库&#xff1f; 1 准备工作2 准备站点证书2.1 创建CA私钥2.2 创建CA根证书请求文件2.3 配置CA根证书2.4 签发根证书2.5 生成站点SSL私钥2.6 私钥生成证书请求文件2.7 配置证书2.8 签署站点SSL证书 3 配置私有仓…

低代码行业的发展真的可以让复杂的代码编写一去不复返?

前言 传统的软件开发过程往往需要耗费大量的时间和精力&#xff0c;因为开发人员需编写复杂的代码以完成各种功能。 低代码行业的发展&#xff0c;正好解决了这个问题&#xff0c;让复杂的代码编写一去不复返了。 文章目录 前言引入强大的平台总结 引入 低代码平台 是一种通过可…

Go基础篇:接口

目录 前言✨一、什么是接口&#xff1f;二、空接口 interface{}1、eface的定义2、需要注意的问题 三、非空接口1、iface的定义2、itab的定义3、itab缓存 前言✨ 前段时间忙着春招面试&#xff0c;现在也算告一段落&#xff0c;找到一家比较心仪的公司实习&#xff0c;开始慢慢回…

yum和repo详细解析

目录 一、rpm、yum、repo 二、repo文件详细解析 三、常用命令 四、更改epel.repo为清华源 一、rpm、yum、repo RPM RPM(Red-hat Package Manager)&#xff0c;是一个由红帽最早开发出来的包管理器&#xff0c;目前已经是大多数Linux发行的默认包管理器。RPM管理的包都是以…

程序员一个月拿两万,得知卖猪肉可以赚五万,你是选择做程序员还是卖猪肉?

在知乎上看到这么个帖子&#xff0c;觉得挺有意思&#xff0c;大家一起瞧瞧&#xff1f; 对此&#xff0c;我也看到了许多犀利的回答哈 **A&#xff1a;**我反过来问你&#xff0c;如果一对夫妇卖猪肉一个月只能挣一万&#xff0c;听说一名程序员一个月拿五万&#xff0c;他们…

刷题day66:目标和

题意描述&#xff1a; 给你一个整数数组 nums 和一个整数 target 。 向数组中的每个整数前添加 或 - &#xff0c;然后串联起所有整数&#xff0c;可以构造一个 表达式 &#xff1a; 例如&#xff0c;nums [2, 1] &#xff0c;可以在 2 之前添加 &#xff0c;在 1 之前添…