C++类与对象(6)—初始化列表、explicit关键字、static成员

目录

一、初始化列表 

1、定义 

2、注意事项

3、尽量使用初始化列表初始化

4、初始化顺序

二、 explicit关键字

1、定义

2、特点

三、static成员

1、定义 

2、特性 

3、例题


一、初始化列表 

下面这段代码可以正常编译:

class A {
private:int _a1;//成员声明int _a2;
};
int main()
{A a;//对象整体定义return 0;
}

如果加上一个const类型的成员变量_x,编译就无法通过。 

class A {
private:int _a1;int _a2;const int _x;
};
int main()
{A a;return 0;
}

这是因为const变量必须在定义的位置初始化,否则编译不通过。

class A {
private:int _a1;//声明int _a2;const int _x;
};

在private作用域中,const变量和两个int变量都是成员变量的声明,如果我们声明const变量,一定要对它进行定义,那我们在哪定义呢?

C++11之后可以在声明位置为变量赋初值。

const int _x = 0;

那在C++11之前,也有解决方法,给每个成员变量找一个位置对其进行定义,这样就解决了变量初始化的问题,这个位置使用初始化列表进行初始化赋值。

1、定义 

初始化列表:以一个冒号开始,接着是一个以逗号分隔的数据成员列表,每个"成员变量"后面跟一个放在括号中的初始值或表达式。
这样就解决了const成员变量初始化问题。
class A {
public:A():_x(1){}
private:int _a1;int _a2;const int _x;
};
int main()
{A a;return 0;
}

只要对象调用构造函数,初始化列表是它所有成员变量定义的位置。
不管是否显示在初始化列表写,那么编译器每个变量都会初始化列表定义初始化。

class A {
public:A():_x(1),_a1(6){}
private:int _a1 = 1;int _a2 = 2;const int _x;
};
int main()
{A a;return 0;
}

在初始化列表中初始化的变量,不使用缺省值;没有使用初始化列表的变量,使用缺省值。 

2、注意事项

  1. 每个成员变量在初始化列表中只能出现一次(初始化只能初始化一次)
  2. 类中包含以下成员,必须放在初始化列表位置进行初始化:
  • 引用成员变量
  • const成员变量
  • 自定义类型成员(且该类没有默认构造函数时)
class B {
public:B():_b(0){cout << "B()" << endl;}
private:int _b;
};class A {
private:B _bb;
};
int main()
{A aa;return 0;
}

 这里的aa的成员变量自定义类型_bb是可以调用它的默认构造函数的初始化列表进行初始化。

 默认构造可以是无参或全缺省的。

class B {
public:B(int n) :_b(0)//会报错B(int n=9) :_b(0)//全缺省B( ) :_b(0)//无参
private:int _b;
};

3、尽量使用初始化列表初始化

因为不管你是否使用初始化列表,对于自定义类型成员变量, 一定会先使用初始化列表初始化。

 下面看一个用两个栈实现的队列。

typedef int DataType;
class Stack
{
public:Stack(size_t capacity = 10){cout << "Stack(size_t capacity = 10)" << endl;_array = (DataType*)malloc(capacity * sizeof(DataType));if (nullptr == _array){perror("malloc申请空间失败");exit(-1);}_size = 0;_capacity = capacity;}void Push(const DataType& data){_array[_size] = data;_size++;}Stack(const Stack& st){cout << "Stack(const Stack& st)" << endl;_array = (DataType*)malloc(sizeof(DataType)*st._capacity);if (nullptr == _array){perror("malloc申请空间失败");exit(-1);}memcpy(_array, st._array, sizeof(DataType)*st._size);_size = st._size;_capacity = st._capacity;}~Stack(){cout << "~Stack()" << endl;if (_array){free(_array);_array = nullptr;_capacity = 0;_size = 0;}}private:DataType *_array;size_t    _size;size_t    _capacity;
};class MyQueue
{
public:MyQueue(int pushN, int popN):_pushST(pushN), _popST(popN){}private:Stack _pushST;Stack _popST;int _size = 0;
};int main()
{	MyQueue q(2, 3);return 0;
}

在调试中可以看到,这里的23分别作为参数传递给MyQueue的构造函数,通过初始化列表对这两个成员变量进行初始化。

 如果我们使用这种无参的构造函数对MyQueue对象初始化呢?

class MyQueue
{
public:MyQueue(){}private:Stack _pushST;Stack _popST;int _size = 0;
};

可以看到,如果我们不写初始化列表,MyQueue类也可以调用Stack的默认构造函数对两个Stack类的对象进行初始化,不写MyQueue的构造函数也会使用同样方式初始化,本质上一样。

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

_a2比_a1先声明,所以_a2比_a1先在初始化列表中初始化,_a2初始化时_a1还没初始化,所以_a2是随机值。

二、 explicit关键字

class A {
public:A(int a):_a1(a){}
private:int _a2;int _a1;
};
int main()
{A aa1(1);	//构造函数A aa2 = 1;	//隐式类型转换int i = 1;	double d = i;//隐式类型转换return 0;
}

默认情况下,这里的隐式类型转换都会借助额外创建的临时变量实现,通过构造创建临时变量,然后拷贝构造给变量赋值的过程被优化为直接构造,下一篇文章详细讲解优化过程。

在这两种情况下,临时变量的创建是为了完成类型转换的过程。这些临时变量在转换完成后会被销毁,对于程序的其他部分是不可见的。这种临时变量的创建和销毁是由编译器自动处理的,无需手动干预。

  • A aa2 = 1; 这里发生了从int到A的隐式类型转换。编译器会自动调用A类的构造函数来创建一个临时的A对象,然后将整数值1传递给构造函数作为参数。这个临时的A对象会被复制到aa2中,完成隐式类型转换。

  • double d = i; 这里发生了从int到double的隐式类型转换。编译器会创建一个临时的double变量,并将整数变量i的值复制到这个临时变量中。然后,这个临时的double变量的值会被赋给变量d,完成隐式类型转换。

拷贝构造也属于构造,也可以使用初始化列表,但下面的成员变量会调用拷贝构造吗?

class A
{
public:A(int a):_a1(a){cout << "A(int a)" << endl;}A(const A& aa):_a1(aa._a1){cout << "A(const A& aa)" << endl;}private:int _a2;int _a1;
};int main()
{A aa1(1);	//构造函数A aa2 = 1;	//隐式类型转换return 0;
}

输出结果发现没有调用引用类型的拷贝构造。

 

这是因为C++中编译器会对自定义类型的进行优化, 将构造+拷贝+优化的过程优化成一个构造。

那下面的代码中,为什么第一个会报错,第二个没问题呢? 

A& ref = 10;
const A& ref = 10;
  • 这是因为在C++中,当你声明一个引用(比如 A& ref)并试图将其初始化为一个右值(比如一个临时对象或一个字面量),编译器通常会报错。这是因为非const引用不能绑定到右值上,防止对临时对象的非常量引用,因为这可能导致对临时对象的意外修改,从而导致不确定的行为。但是,当你声明一个常量引用(比如 const A& ref),编译器允许这种绑定,因为常量引用可以绑定到右值上。
  • 在上述代码中,const A& ref = 10; 这行代码中的 10 是一个整数字面量,是一个右值。你尝试将这个右值绑定到引用 ref 上。由于 ref 被声明为 const A&,它是一个常量引用,所以编译器允许这种绑定,并调用 A 类的构造函数 A(int a) 来创建一个临时的 A 对象,然后将 ref 绑定到这个临时对象上。

1、定义

构造函数不仅可以构造与初始化对象,对于单个参数或者除第一个参数无默认值其余均有默认值
的构造函数,还具有类型转换的作用。
对于单参构造函数:没有使用explicit修饰,具有类型转换作用
explicit修饰构造函数,禁止类型转换---explicit去掉之后,代码可以通过编译。

 对于刚刚的代码,如果在构造函数前加explicit程序会怎么样呢?

class A{
public:explicit A(int a):_a1(a){cout << "A(int a)" << endl;}A(const A& aa):_a1(aa._a1){cout << "A(const A& aa)" << endl;}private:int _a2;int _a1;
};
int main()
{A aa1(1);	//构造函数A aa2 = 1;	//隐式类型转换const A& ref = 10;return 0;
}

 这两段代码会报错,程序禁止类型转换。

 

2、特点

class A
{
public://explicit A(int a)A(int a):_a1(a){cout << "A(int a)" << endl;}//explicit A(int a1, int a2)A(int a1, int a2):_a1(a1), _a2(a2){}private:int _a2;int _a1;
};
int main()
{// 单参数构造函数 C++98A aa1(1);	//构造函数A aa2 = 1;	//隐式类型转换// 多参数构造函数 C++11A aa2(1, 1);//A aa3= 2,2;//C98不支持A aa3 = { 2, 2 };//C++11支持return 0;
}
  1. A aa1(1); 这是直接调用单参数构造函数创建对象的例子。

  2. A aa2 = 1; 这是一个隐式类型转换的例子。这里,整数1被隐式地转换为类A的一个对象。这是因为类A定义了一个接受int类型参数的构造函数,因此编译器会自动调用该构造函数来创建一个临时的A对象,并将其赋值给aa2。

  3. A aa2(1, 1); 这是直接调用双参数构造函数创建对象的例子。

  4. A aa3 = { 2, 2 }; 这是C++11引入的列表初始化的例子。这种方式可以用来初始化对象,而不需要显式地调用构造函数。

explicit关键字用于阻止编译器进行不希望发生的隐式类型转换。如果你将构造函数前面的注释去掉,使得构造函数前面有explicit关键字,那么像A aa2 = 1;这样的隐式类型转换就会被禁止,编译器会报错。

例如,如果你将单参数构造函数改为explicit A(int a),那么A aa2 = 1;这行代码就会导致编译错误,因为编译器被禁止进行从int到A的隐式类型转换。你必须显式地调用构造函数,像A aa2(1);这样。

总的来说,explicit关键字可以帮助你控制类型转换,防止因为不希望的隐式类型转换而导致的错误。

三、static成员

实现一个类,计算程序中创建了多少类对象

int count = 0;
class A
{
public:A(int a = 0){++count;}A(const A& aa){++count;}
};
void func(A a)
{}
int main()
{A aa1;A aa2(aa1);func(aa1);A aa3 = 1;cout << count << endl;return 0;
}

造成了命名冲突的问题,因为C++的xutility文件里有个函数count与我们定义的全局变量count冲突了。

我们可以不展开std,只调用需要用的流输入输出即可。

#include <iostream>
//using namespace std;
using std::cout;
using std::endl;

成功输出: 

C++为了解决上述问题,同时可以将std展开,将count作为类的static修饰的成员即可实现。

1、定义 

  • 声明为static的类成员称为类的静态成员,用static修饰的成员变量,称之为静态成员变量;
  • 用static修饰的成员函数,称之为静态成员函数。
  • 静态成员变量一定要在类外进行初始化。
  • 静态成员不属于某个对象,所于所有对象,属于整个类。
  • 静态成员变量的初始化通常在类外部进行。
class A
{
public:A(int a = 0){++count;}A(const A& aa){++count;}int Getcount(){return count;}
private:static int count; // 此处为声明int _a = 0;
};int A::count = 0; // 定义初始化void func(A a)
{}

当我们想输出时:

int main()
{A aa1;A aa2(aa1);func(aa1);A aa3 = 1;cout << A::Getcount() << endl;return 0;
}
输出语句会报错:
如果想要输出,可以使用静态成员函数。
	//静态成员函数 没有this指针static int Getcount(){// _a++; // 不能直接访问非静态成员return count;}

成功输出:

下面语句创建出了多少个类对象?
A aa4[10];

输出结果:

2、特性 

  • 静态成员为所有类对象所共享,不属于某个具体的对象,存放在静态区
  • 静态成员变量必须在类外定义,定义时不添加static关键字,类中只是声明
  • 类静态成员即可用 类名::静态成员 或者 对象.静态成员 来访问
  • 静态成员函数没有隐藏的this指针,不能访问任何非静态成员
  • 静态成员也是类的成员,受public、protected、private 访问限定符的限制

3、例题

链接如下:

求1+2+3+...+n_牛客题霸_牛客网 (nowcoder.com)

  • 下面这段代码实现了一个类 Sum 和一个类 Solution,其中 Sum 类用于计算从1到n的累加和,而 Solution 类则使用 Sum 类来计算给定整数n的累加和。 
  • 这种设计利用了类的构造函数和静态成员变量的特性,实现了累加和的计算和获取。
class Sum{
public:Sum(){_sum+=_i;_i++;}static int Getsum(){return _sum;}
private:static int _sum;static int _i;
};
int Sum::_sum = 0;
int Sum::_i = 1;
class Solution {
public:int Sum_Solution(int n) {Sum a[n];return Sum::Getsum();}
};

首先,让我们逐步解释 Sum 类的实现:

  • Sum 类有两个静态成员变量 _sum 和 _i,分别用于保存累加和和当前的计数器值。
  • 构造函数 Sum() 是一个无参构造函数,每次被调用时,它会将当前计数器值 _i 加到累加和 _sum 中,并将计数器 _i 自增1。
  • 静态成员函数 Getsum() 用于获取累加和 _sum 的值。

接下来,我们来看 Solution 类的实现:

  • Solution 类中的成员函数 Sum_Solution(int n) 接受一个整数 n 作为参数,并返回从1到n的累加和。
  • 在 Sum_Solution 函数中,我们创建了一个名为 a 的 Sum 类型的数组,数组的大小为 n
  • 由于 Sum 类的构造函数会在创建对象时自动调用,因此创建数组 a 的过程中,会依次调用 Sum 类的构造函数,从而实现了从1到n的累加和的计算。
  • 最后,我们通过调用 Sum::Getsum() 函数来获取累加和的值,并将其作为函数的返回值。

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

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

相关文章

CGAN原理讲解与源码

1.CGAN原理 生成器&#xff0c;输入的是c和z&#xff0c;z是随机噪声&#xff0c;c是条件&#xff0c;对应MNIST数据集&#xff0c;要求规定生成数字是几。 输出是生成的虚假图片。 判别器的输入是 1.生成器输出的虚假图片x; 2.对应图片的标签c 来自真实数据集&#xff0c;且…

【深度学习】概率图模型(一)概率图模型理论简介

文章目录 一、概率图模型1. 联合概率表2. 条件独立性假设3. 三个基本问题 二、模型表示1. 有向图模型&#xff08;贝叶斯网络&#xff09;2. 无向图模型&#xff08;马尔可夫网络&#xff09; 三、学习四、推断 概率图模型&#xff08;Probabilistic Graphical Model&#xff0…

ROS知识:卡尔曼滤波

https://en.wikipedia.org/wiki/Kalman_filter 一、提要 在卡尔曼滤波的相关技术文献中,其数学表达看起来都非常晦涩和不透明。这很糟糕,如果您以正确的方式看待卡尔曼滤波器,它实际上非常简单易懂。这里的叙述简单,先决条件也很简单;您所需要的只是对概率和矩阵的基本了解…

【C++】友元

1. 友元的概念 友元的目的就是让一个函数或者类 访问另一个类中私有成员。 友元的三种实现&#xff1a; 全局函数做友元类做友元成员函数做友元 2. 友元的实现方式 2.1 全局函数做友元 #include <iostream> using namespace std; class Building {// 告诉编译器 go…

【Android Gradle】之一小时 Gradle及 wrapper 入门

&#x1f604;作者简介&#xff1a; 小曾同学.com,一个致力于测试开发的博主⛽️&#xff0c;主要职责&#xff1a;测试开发、CI/CD 如果文章知识点有错误的地方&#xff0c;还请大家指正&#xff0c;让我们一起学习&#xff0c;一起进步。 &#x1f60a; 座右铭&#xff1a;不…

PC删除数据,并提示删除成功

<template<el-button size"mini" type"text">分配权限</el-button><el-button size"mini" type"text" click"btnEditRow(row)">编辑</el-button ><el-popconfirmtitle"这是一段内容确定…

计算机毕业设计springboot+vue高校田径运动会报名管理系统61s38

高校田径运动会管理采用java技术&#xff0c;基于springboot框架&#xff0c;mysql数据库进行开发&#xff0c;实现了首页、个人中心、运动员管理、裁判员管理、场地信息管理、项目类型管理、比赛项目管理、比赛报名管理、比赛成绩管理、通知公告管理、留言板管理、交流论坛、系…

微软发布了Orca 2,一对小型语言模型,它们的性能超越了体积更大的同类产品

尽管全球目睹了OpenAI的权力斗争和大规模辞职&#xff0c;但作为AI领域的长期支持者&#xff0c;微软并没有放慢自己的人工智能努力。今天&#xff0c;由萨提亚纳德拉领导的公司研究部门发布了Orca 2&#xff0c;这是一对小型语言模型&#xff0c;它们在零样本设置下对复杂推理…

数据结构---顺序表

文章目录 线性表线性表的定义线性表分类 顺序表顺次表的存储结构实现顺序表的主要接口函数初始化顺序表顺序表尾插顺序表尾删顺序表头插顺序表头删在指定位置插入数据在指定的位置删除数据头插&#xff0c;头删&#xff0c;尾插&#xff0c;尾删新写法打印顺序表销毁顺序表 线性…

基于halo框架采用docker-compose快速部署个人博客

halo快速部署个人博客 技术方案 dockerdocker-composenginxmysql halo简介 Halo是一款现代化的开源博客/CMS系统&#xff0c;所有代码开源在GitHub上且处于积极维护状态。它是基于 Java Spring Boot 构建的&#xff0c;易于部署&#xff0c;支持REST API、模板系统、附件系…

关于微服务的思考

目录 什么是微服务 定义 特点 利弊 引入时机 需要哪些治理环节 从单体架构到微服务架构的演进 单体架构 集群和垂直化 SOA 微服务架构 如何实现微服务架构 服务拆分 主流微服务解决方案 基础设施 下一代微服务架构Service Mesh 什么是Service Mesh&#xff1f…

python实现自动刷平台学时

背景 前一阵子有个朋友让我帮给小忙&#xff0c;因为他每学期都要看视频刷学时&#xff0c;一门平均需要刷500分钟&#xff0c;一学期有3-4门需要刷的。 如果是手动刷的话&#xff0c;比较麻烦&#xff0c;能否帮他做成自动化的。搞成功的话请我吃饭。为了这顿饭&#xff0c;咱…

京东秒杀之商品展示

1 在gitee上添加.yml文件 1.1 添加good-server.yml文件 server:port: 8084 spring:datasource:url: jdbc:mysql://localhost:3306/shop_goods?serverTimezoneGMT%2B8driverClassName: com.mysql.cj.jdbc.Drivertype: com.alibaba.druid.pool.DruidDataSourceusername: rootpa…

多功能音乐沙漏的设计与实现

【摘要】随着当今社会快节奏生活的发展&#xff0c;当代大学生越来忽视时间管理的重要性&#xff0c;在原本计划只看几个视频只玩几个游戏的碎片化娱乐中耗费了大量的时光&#xff0c;对于自己原本的学习生活产生了巨大的影响。为更加有效的反映时间的流逝&#xff0c;特设计该…

第十七章 解读PyTorch断点训练(工具)

主要有以下几方面的内容&#xff1a; 对于多步长训练需要保存lr_schedule初始化随机数种子保存每一代最好的结果 简单详细介绍 最近在尝试用CIFAR10训练分类问题的时候&#xff0c;由于数据集体量比较大&#xff0c;训练的过程中时间比较长&#xff0c;有时候想给停下来&…

Gitee上传代码教程

1. 本地安装git 官网下载太慢&#xff0c;我们也可以使用淘宝镜像下载&#xff1a;CNPM Binaries Mirror 安装成功以后电脑会有Git Bush标识&#xff0c;空白处右键也可查看。 2. 注册gitee账号&#xff08;略&#xff09; 3. 创建远程仓库 4. 上传代码 4.1 在项目文件目录…

go当中的channel 无缓冲channel和缓冲channel的适用场景、结合select的使用

Channel Go channel就像Go并发模型中的“胶水”&#xff0c;它将诸多并发执行单元连接起来&#xff0c;或者正是因为有channel的存在&#xff0c;Go并发模型才能迸发出强大的表达能力。 无缓冲channel 无缓冲channel兼具通信和同步特性&#xff0c;在并发程序中应用颇为广泛。…

坚鹏:贵州银行西南财经大学零售业务数字化转型与场景营销策略

中国银保监会2022年1月正式发布了中国银保监会发布《关于银行业保险业数字化转型的指导意见》&#xff0c;这标准着中国银行业从局部的数字化转型向全面的数字化转型转变&#xff0c;进一步加速了银行数字化转型高潮的到来。 《关于银行业保险业数字化转型的指导意见》提出明确…

【教学类-06-12】20231126 (二)三位数 如何让加减乘除题目从小到大排序(以0-110之间加法为例,做正序排列用)

结果展示 背景需求&#xff1a; 二位数&#xff1a;去0 三位数&#xff08;需要排除很多0&#xff09; 解决思路 一、把数字改成三位数 二、对数组内的题目&#xff0c;8种可能性进行去“0”处理 1、十位数&#xff08;去百位数0&#xff09;十位数&#xff08;去百位数0&am…

数据增强让模型更健壮

在做一些图像分类训练任务时,我们经常会遇到一个很尴尬的情况,那就是: 明明训练数据集中有很多可爱猫咪的照片,但是当我们给训练好的模型输入一张戴着头盔的猫咪进行测试时,模型就不认识了,或者说识别精度很低。 很明显,模型的泛化能力太差,难道戴着头盔的猫咪就不是猫…