C++类与对象(四):再谈构造函数(详解初始化列表)、Static成员

news2024/11/13 21:39:24

上次把默认的成员函数部分梳理完毕了:C++初阶类与对象(三):详解复制构造函数和运算符重载
今天接着讲下面的内容:


文章目录

  • 1.再谈构造函数
    • 1.1构造函数体赋值
    • 1.2初始化列表
      • 1.2.1格式和概念
      • 1.2.2由来
        • 情况1
        • 情况2
      • 1.2.3特性
      • 1.2.4特殊情况
    • 1.3explicit关键字
  • 2. static成员
    • 2.1概念与引入
    • 2.2特性


1.再谈构造函数

1.1构造函数体赋值

根据之前介绍的内容:在创建对象时,编译器通过调用构造函数,给对象中各个成员变量一个合适的初始值,我们之前使用的构造函数都叫做函数体内赋初值

class Date
{
public:
	Date(int year = 2024, int month = 1, int day = 1)//使用全缺省,也是默认构造函数
	{
		//函数体内初始化,在函数体内进行赋值
		_year = year;
		_month = month;
		_day = day;
	}
private:
	int _year;//变量声明
	int _month;
	int _day;
};

虽然上述构造函数调用之后,对象中已经有了一个初始值,但是不能将其称为对对象中成员变量的初始化,构造函数体中的语句只能将其称为赋初值,而不能称作初始化。因为初始化只能初始化一次,而构造函数体内可以多次赋值。

初始化与赋值区别:

  1. 初始化是在创建变量时为其赋予一个初始值。在构造函数中,初始化通常是在对象创建时对成员变量进行赋值。初始化可以在变量声明时进行,也可以在构造函数的初始化列表中进行(下面就介绍)。
  2. 赋值是在变量已经存在的情况下改变变量的值。赋值操作符=用于将一个值赋给一个已经存在的变量
  • 初始化是在变量创建时进行的,而赋值是在变量已经存在的情况下进行的
  • 初始化可以只进行一次,而赋值可以进行多次
  • 在一些情况下,初始化可能比赋值更加高效,因为它可以在对象创建时直接将初始值传递给对象,而不需要额外的操作

1.2初始化列表

1.2.1格式和概念

初始化列表:成员变量定义处

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

class Date
{
public:
	Date(int year = 2024, int month = 1, int day = 1)//使用全缺省,也是默认构造函数
		:_year(year)
		,_month(month)
		,_day(day)  //初始化列表
	{

	}
private:
	int _year;
	int _month;
	int _day;
};

那大家可能有疑问了? 之前函数体内赋值不是用的好好的嘛,来这个干嘛? 现在就来解释:

1.2.2由来

情况1
class Date
{
public:
	Date(int year = 2024, int month = 1, int day = 1)
		:_year(year)
		,_month(month)
		,_day(day)  //这三个可以在这,也可以在函数体内
		,_ref(year)
		,_n(1)  //这两个必须在这里
	{

	}
    //两个地方可以混着用,这样也行  
	//Date(int year = 2024, int month = 1, int day = 1)
	//	: _ref(year)
	//	, _n(1)  //这两个必须在这里
	//{
	//	_year = year;//没有在初始化列表显示出来定义,但是也会定义和初始化,不过内置类型随机值。
                                            //自定义类型调用自己的默认构造函数
	//	_month = month;
	//	_day = day;//这三个可以在这,也可以在初始化列表内
	//}
    
private:
	int _year;//这些都是声明,还没有空间
	int _month;
	int _day;

	int& _ref;//引用,必须在定义的时候初始化
	const int _n;//常量,必须在定义的时候初始化
};

int main()
{
	//实例化对象
	Date d1;//此时才定义,但是对象整体定义; 那每个成员在哪里定义呢?——就在初始化列表
	return 0;
}

可以知道的是:在进去函数体之前,定义和初始化都已经完成了,函数体进行的只是单纯的赋值操作。

所有的初始化行为都是在初始化列表内完成的。如果在初始化列表里没有出现的话一般是会在初始化列表给他初始化为默认值(随机值或自己给的缺省值)

之前我们也用过缺省值:

class Date
{
public:
	//两个地方可以混着用,这样也行
	Date(int year = 2024, int month =1, int day = 1)
		: _ref(year)
		, _n(1)  //这里给1,看结果
	{
		_year = year;
		_month = month;
		_day = day;//这三个可以进行赋值
	}
private:
	int _year=1;//只给_year缺省值
	int _month;
	int _day;

	int& _ref;//引用,必须在定义的时候初始化
	const int _n=2;//这里给2
};

如果在初始化列表里进行了显示地初始化,那就按照列表里进行(最优先); 没有那才会用缺省值;连缺省值都没有那就随机值了。

上述赋值结果:

请添加图片描述

情况2
class Stack
{
public:
	Stack(int capacity)
	{
		//.......
	}
	//没有默认构造函数了
private:
	int* _a;
	int _top;
	int _capacity;
};

class MyQueue
{
public:
	//此时都是自定义类型,但是又没有默认构造函数;或者有但是不想用。就要自己初始化
	MyQueue()
		:_s1(4)//自己显示地初始化
		, _s2(5)
	{

	}
private:
	Stack _s1;
	Stack _s2;
};

如果自定义类型没有默认构造函数。解决方案:

  1. 写出来默认构造
  2. 在初始化列表处显示地写出来

1.2.3特性

  1. 每个成员变量在初始化列表中只能出现一次(初始化只能初始化一次)
  2. 类中包含以下成员,必须放在初始化列表位置进行初始化:(在由来里讲了)
    • 引用成员变量
    • const成员变量
    • 自定义类型成员(且该类没有默认构造函数时)
  1. 尽量使用初始化列表初始化,因为不管你是否使用初始化列表,对于自定义类型成员变量,一定会先使用初始化列表初始化
  2. 成员变量在类中声明次序就是其在初始化列表中的初始化顺序,与其在初始化列表中的先后次序无关
  3. 解决的问题
    • 必须在定义的地方显示地初始化:引用 const
    • 没有默认构造函数的自定义成员
    • 有些自定义成员想要自己控制自己的初始化

1.2.4特殊情况

class Stack
{
public:
	Stack(int capacity=3)
	{
		cout << "调用了Stack的默认构造函数";
		//.......
	}
private:
	int* _a;
	int _top;
	int _capacity;
};

class MyQueue
{
public:

private:
	Stack _s1;
	Stack _s2;//这俩可调用Stack类的默认构造
	int _size = -1;//给了缺省值
	//此时可以简单点理解:该类的默认构造函数对于自定义就去调用他们的,对于内置使用缺省或随机值
};
class Stack
{
public:
	Stack(int capacity=3)
	{
		cout << "调用了Stack的默认构造函数";
		//.......
	}
	//没有默认构造函数了
private:
	int* _a;
	int _top;
	int _capacity;
};

class MyQueue
{
public:
	MyQueue()//有没有效果一样,没写就按照默认构造函数那老一套
	{ }  //写了初始化列表一定会走,但没有显示的写那也是老一套
private:
	Stack _s1;
	Stack _s2;//这俩可调用Stack类的默认构造
	int _size = -1;//给了缺省值
	
};

请添加图片描述

1.3explicit关键字

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

用explicit修饰构造函数,将会禁止构造函数的隐式转换

  1. 构造函数是单参数
class A
{
public:
	A(int a = 0)
		:_a(a)
	{ }
private:
	int _a;
};

int main()
{
	A a1(1);//这样创建对象大家都知道

	A a2 = 2;//这样也可以:内置类型对象隐式转换为自定义类型对象
	         //能这样做,是A的int单参数构造函数支持的
			 //其实隐式转换中间产生一个临时变量,临时变量是A类型的

	const A& ra = 3;//给临时变量起别名,这时临时变量会在引用的作用域结束时销毁
	return 0;
}

请添加图片描述

  1. 除第一个参数无默认值其余均有默认值的多参构造函数
class Date
{
public:
	 Date(int year, int month = 1, int day = 1)
		:_year(year)
		,_month(month)
		,_day(day)
	{ }

private:
	int _year;
	int _month;
	int _day;
};

int main()
{
	Date d1(2024, 1, 6);
    Date d2=2024;//
    //Date d2 = { 2024,1,1 };这样也行
	Date d3 = (2024, 1, 1);//这样是逗号表达式子<==> Date d3=1 <==> Date d3=(Date)1 <==> Date d3(1)
	return 0;
}

请添加图片描述

  1. 全缺省构造函数
class Date
{
public:
	// explicit修饰构造函数,禁止类型转换
	 Date(int year=1, int month = 1, int day = 1)
		:_year(year)
		,_month(month)
		,_day(day)
	{ }

private:
	int _year;
	int _month;
	int _day;
};

int main()
{
	Date d1;
	Date d2 = { 2024,2 };//从左到右依次赋值
	Date d3 = { 2024,2,2 }; //这样也行
	Date d4 = 2024;
	return 0;
}

请添加图片描述


2. static成员

2.1概念与引入

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

用static修饰的成员函数,称之为静态成员函数。静态成员变量一定要在类外进行初始化(不走初始化列表,不属于单个成员。类里声明,类外定义)

static静态成员变量:属于整个类,属于这个类所有对象。受访问限定符限制

实际上:静态成员函数和静态成员变量,本质上是受限制的全局变量和全局函数(专属这个类,受类域和访问限定符的限制)

#include<iostream>
using namespace std;
class A
{
public:
	A()//无参构造
	{
		count++;
	}
	A(A& a)//拷贝构造
	{
		count++;
	}

	static int count;//类内声明,属于整体(公有)
};
int A::count = 0;//类外定义,就类似于成员函数声明和定义分离

int main()
{
	A aa;
	cout << A::count << endl;//正常大家会想到这样访问
	cout << aa.count << endl;//这样也可以,类比调用成员函数:告诉编译器去那个类里找
}

此时是公有,那如果是私有。要怎么访问呢???

对于count都在类外定义了,为什么不能直接访问呢? 这样就直接以成员函数类比就行

using namespace std;
class A
{
public:
	A()//无参构造
	{
		count++;
	}
	A(A& a)//拷贝构造
	{
		count++;
	}
	int getCount()
	{
		return count;
	}
private:
	static int count;//类内声明,属于整体(私有)
};
int A::count = 0;//类外定义

int main()
{
	A aa;
	cout << aa.getCount()-1 << endl;//因为为了得到count而特地创建了一个对象来调用get函数,要-1;
}

现在count是私有了,就定义了一个getCount函数来得到。但是:为了得到count而特地创建了一个对象来调用get函数(还是有点不合适)

对于对象调用成员函数意义:1. 是告诉编译器getCount在A类里 2. 另一个是传this指针

而编译器在编译阶段遇到变量或者函数,都会去找出处,向上找和全局找(也是命名空间和类域起作用原因)

class A
{
public:
	A()//无参构造
	{
		count++;
	}
	A(A& a)//拷贝构造
	{
		count++;
	}
	static int getCount()//静态成员函数,没有this指针,所以不能访问非静态成员变量
	{
		return count;
	}
private:
	static int count;//类内声明,属于整体(公有)
};
int A::count = 0;//类外定义

int main()
{
	A aa;
	cout << A::getCount()-1 << endl;//可以直接用类名调用
    cout << aa.getCount << endl;//这样也行
}	

2.2特性

根据上面也可总结出一些特性:

  1. 静态成员为所有类对象所共享,不属于某个具体的对象,存放在静态区。它们在类的所有实例之间是唯一的。因此,静态成员函数可以直接访问静态成员变量,因为它们不依赖于特定的对象实例,而是与整个类相关联
  2. 静态成员变量必须在类外定义,定义时不添加static关键字,类中只是声明
  3. 类静态成员即可用 类名::静态成员 或者 对象.静态成员 来访问
  4. 静态成员函数没有隐藏的this指针,不能访问任何非静态成员
  5. 静态成员也是类的成员,受public、protected、private 访问限定符的限制
  6. 静态成员函数,没有this指针,所以不能访问非静态成员变量

实际上:静态成员函数和静态成员变量,本质上是受限制的全局变量和全局函数(专属这个类,受类域和访问限定符的限制)


这次就先到这里啦,下次类与对象的内容也要告一段落了,感谢大家支持!!!

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

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

相关文章

浏览器插件:WebScraper基本用法和抓取页面内容(不会编程也能爬取数据)

Web Scraper 是一个浏览器扩展&#xff0c;用于从页面中提取数据(网页爬虫)。对于简单或偶然的需求非常有用&#xff0c;例如正在写代码缺少一些示例数据&#xff0c;使用此插件可以很快从类似的网站提取内容作为模拟数据。从 Chrome 的插件市场安装后&#xff0c;页面 F12 打开…

flink学习之水位线

什么是水位线 在事件时间语义下&#xff0c;我们不依赖系统时间&#xff0c;而是基于数据自带的时间戳去定义了一个时钟&#xff0c; 用来表示当前时间的进展。于是每个并行子任务都会有一个自己的逻辑时钟&#xff0c;它的前进是靠数 据的时间戳来驱动的。 我们可以把时钟也以…

【想要安利给所有人的开发工具】一款写笔记的工具——语雀

目录 &#x1f4d5;开篇 ✍使用感受 &#x1f44d;语雀的常用功能 1、导出成图片 2、导出为PDF 3、代码的模块 4、流程图 ​5、画板类 6、程序员专用区 ​7、布局和样式 8、菜单栏的功能 9、其余功能&#xff08;很多&#xff09; &#x1f697;为什么推荐语雀 &…

(二)CarPlay集成开发之苹果的iAP协议

文章目录 概要协议格式鉴权流程CarPlay中的iAP2协议应用小结 概要 iAP2协议是由苹果公司定义的一种数据通信协议&#xff0c;主要用于苹果设备认证外设&#xff0c;以及与外设数据交换的一种协议 协议格式 协议格式一共分为三种类型&#xff0c;分别为握手包&#xff0c;链路…

lattice Diamond Programmer程序下载

Lattice Diamond Programmer Diamond Programmer程序下载1 Diamond Programmer启动2 Diamond Programmer程序烧写3 Cannot Identify Device错误解决 Diamond Programmer程序下载 Diamond Programmer适用于Lattice公司的FPGA器件与CPLD器件的程序下载&#xff0c;其下载步骤如下…

如何才能拥有比特币 - 01 ?

如何才能拥有BTC 在拥有 BTC 之前我们要先搞明白 BTC到底保存在哪里&#xff1f;我的钱是存在银行卡里的&#xff0c;那我的BTC是存在哪里的呢&#xff1f; BTC到底在哪里&#xff1f; 一句话概括&#xff0c;BTC是存储在BTC地址中&#xff0c;而且地址是公开的&#xff0c;…

Python项目——搞怪小程序(PySide6+Pyinstaller)

1、介绍 使用python编写一个小程序&#xff0c;回答你是猪吗。 点击“是”提交&#xff0c;弹窗并退出。 点击“不是”提交&#xff0c;等待5秒&#xff0c;重新选择。 并且隐藏了关闭按钮。 2、实现 新建一个项目。 2.1、设计UI 使用Qt designer设计一个UI界面&#xff0c…

android 开发 W/TextToSpeech: speak failed: not bound to TTS engine

问题 笔者使用TTS(TextToSpeech)对于文本内容进行语音播报&#xff0c;控制台报错 android 开发 speak failed:not bound to TTS engine详细问题 笔者核心代码&#xff1a; import android.os.Bundle; import android.speech.tts.TextToSpeech; import android.speech.tts.…

react native Gradle的原国外地址、本地下载、国内阿里腾讯镜像三种下载配置

一、国外地址&#xff1a;&#xff08;初始项目默认&#xff09; 下载地址&#xff1a;https://services.gradle.org/distributions/ 文件地址见下图&#xff1a; 注意&#xff1a;这个地址下载十次就有九次是连接超时&#xff0c;建议换另外两种方法 二、下载到本地&#x…

LLM:ALiBi - 给注意力加上线性偏置

论文&#xff1a;https://arxiv.org/pdf/2108.12409.pdf 代码&#xff1a;https://github.com/ofirpress/attention_with_linear_biases 发表&#xff1a;2021 长度外推 参考&#xff1a;https://spaces.ac.cn/archives/9431#ALIBI 长度外推性是一个训练和预测的长度不一致…

tomcat原理模拟和tomcat优化

1、tomcat实现原理 servlet 没有主方法main&#xff0c;依赖tomcat才能运行&#xff0c;因为tomcat 有主方法main&#xff0c;由java编写 servlet中doGet和doPost方法属于非静态方法&#xff0c;只能依托new对象存在&#xff0c;tomcat无法new出来对象&#xff0c;因此tomcat…

手机与电脑更改IP地址怎么使用代理IP?

在现代互联网时代&#xff0c;代理IP已成为许多人日常生活和工作中不可或缺的一部分。通过代理IP&#xff0c;用户可以隐藏自己的真实IP地址&#xff0c;并获得更好的网络体验。本文将详细介绍如何在手机和电脑上更改IP地址并使用代理IP。 一、手机使用代理IP 1. 打开手机设置&…

1.C语言——基础知识

C语言基础知识 1.第一个C语言程序2.注释3.标识符4.关键字5.数据类型6.变量7.常量8.运算符9.输入输出输入输出 1.第一个C语言程序 C语言的编程框架 #include <stdio.h> int main() {/* 我的第一个 C 程序 */printf("Hello, World! \n");return 0; }2.注释 单行…

MySQL面试题 | 18.精选MySQL面试题

&#x1f90d; 前端开发工程师&#xff08;主业&#xff09;、技术博主&#xff08;副业&#xff09;、已过CET6 &#x1f368; 阿珊和她的猫_CSDN个人主页 &#x1f560; 牛客高级专题作者、在牛客打造高质量专栏《前端面试必备》 &#x1f35a; 蓝桥云课签约作者、已在蓝桥云…

Vue3前端开发,如何获取组件内dom对象以及子组件的属性和方法

Vue3前端开发,借助Ref来获取组件内dom对象&#xff0c;借助defineExpose编译宏可以获取到子组件的属性和方法。 <script setup> import {onMounted, ref} from vue import Base from ./components/Base.vue import SetupDemo from ./components/SetupDemo.vue import Rea…

探索C++中std::string的弱点:你可能未曾注意到的缺点

C中std::string的弱点&#xff1a;你可能未曾注意到的缺点 一、背景二、性能方面的局限三、可变性带来的问题四、内存管理和指针操作五、Unicode和多字节字符集的支持六、其他替代方案七、总结 一、背景 C中std::string是一个非常重要的类&#xff0c;用于表示和处理字符串数据…

无偿分享一个很有用的看源码小技巧

怎么在 idea 里面查看 git 提交记录呢&#xff1f;这个界面是藏在哪里的呢&#xff0c;我的 idea 里面怎么没有呢&#xff1f; 好的&#xff0c;是我疏忽了&#xff0c;我先入为主的认为这个大家应该都知道是怎么来的。 但是确实是有一些同学是不太清楚的&#xff0c;那我这篇…

Java设计模式-单例模式(2)

大家好&#xff0c;我是馆长&#xff01;从今天开始馆长开始对java设计模式的创建型模式中的单例、原型、工厂方法、抽象工厂、建造者的单例模式进行讲解和说明。 单例模式&#xff08;Singleton&#xff09; 定义 某个类只能生成一个实例&#xff0c;该类提供了一个全局访问…

Docker技巧汇总

Docker技巧汇总 前言使用流程安装配置镜像管理创建并运行容器使用容器/常用命令导出和导入查看元数据挂载数据卷端口映射/转发VS Code连接Docker 前言 Docker 是一个开源的应用容器引擎&#xff0c;可以让开发者打包他们的应用以及依赖包到一个轻量级、可移植的容器中&#xf…

2024年第十五届机械与智能制造技术国际会议(ICMIMT 2024)

2024年第十五届机械与智能制造技术国际会议(ICMIMT 2024)2024年5月17-19日 南非 开普敦会议官网&#xff1a; 15TH IEEE-ICMIMT 2024http://www.mimt.us/ 近年来&#xff0c;机械和智能制造技术取得了重大进展。先进计算和传感技术的集成带来了更精确、更高效和自动化的制造过…