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

目录

一、初始化列表 

1、定义 

2、注意事项

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

4、初始化顺序

二、 explicit关键字

1、定义

三、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;
}

默认情况下,这里的隐式类型转换都会借助额外创建的临时变量实现。 

我们使用下面的程序验证一下:

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

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

 

这是因为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;
};

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

 

虽然有多个参数,创建对象时后两个参数可以不传递,没有使用explicit修饰,具有类型转换作用 ,explicit修饰构造函数,禁止类型转换。
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++98
	A aa1(1);	//构造函数
    A aa2 = 1;	//隐式类型转换

	// 多参数构造函数 C++11
	A aa2(1, 1);
    //A aa3= 2,2;//C98不支持
	A aa3 = { 2, 2 };//C++11支持

	return 0;
}

在C++中,构造对象的方式主要有两种:直接初始化和列表初始化。这两种方式在某些情况下会有不同的行为。让我们来看看代码中的两种情况:

A aa2(1, 1);

  • 这是直接初始化的一个例子。
  • 在这种情况下,编译器会查找一个与提供的参数列表最匹配的构造函数。这里,它会匹配 A(int a1, int a2) 构造函数。
  • 这种方式不会触发任何类型转换或隐式调用。

A aa3 = { 2, 2 };

  • 这是列表初始化,在C++11及以后的标准中引入。
  • 列表初始化的行为取决于多种因素,如构造函数的可用性、是否有可用的初始化列表构造函数、是否有需要进行隐式转换的场景等。
  • 在这个特定的例子中,它将尝试使用 A(int a1, int a2) 构造函数,因为这是唯一可以从两个整数参数构造 A 对象的构造函数。
  • 如果类 A 有一个接受 std::initializer_list 的构造函数,这个构造函数将会被优先使用。
  • 列表初始化还有一个优点,它禁止了窄化转换。例如,如果你试图用一个浮点数来初始化一个整数,编译器将会报错。

总的来说,这两行代码在这种情况下的行为是相似的,因为 A 类没有提供接受 std::initializer_list 参数的构造函数,且构造函数 A(int a1, int a2) 能够接受两个整数参数。但是,列表初始化有时候会提供一些额外的安全性和灵活性,尤其是在处理聚合类型(如结构体和数组)时。

三、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;
	}
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、特性 

  1. 静态成员为所有类对象所共享,不属于某个具体的对象,存放在静态区
  2. 静态成员变量必须在类外定义,定义时不添加static关键字,类中只是声明
  3. 类静态成员即可用 类名::静态成员 或者 对象.静态成员 来访问
  4. 静态成员函数没有隐藏的this指针,不能访问任何非静态成员
  5. 静态成员也是类的成员,受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() 函数来获取累加和的值,并将其作为函数的返回值。

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

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

相关文章

企业海外分部,如何实现安全稳定的跨境网络互连?

如今&#xff0c;众多企业广泛采取数字化业务系统&#xff0c;如OA、ERP及CRM等&#xff0c;来提升其业务运营效率。同时&#xff0c;私有云与公有云混合架构也逐渐普及化。 具体来说&#xff0c;很多企业选择将研发系统部署在公司本地的私有云环境&#xff0c;以此确保数据安全…

pgz easyexcel如何给excel文件添加自定义属性

免费API方式 直接上传URL,自定义修改Excel 视频演示【内含接口地址】 https://www.ixigua.com/7304510132812153385 前情提示 | 功能说明 多选仅支持微软office、office365系列Excel。因为WPS宏功能需要企业版且付费生成xlsx、xlsm等文件,office和WPS均可以打开,均可以单…

BTC 复兴:Ordinals 带来创新活力,BitVM 与 BitStream 相继问世

除了备受瞩目的 ETF&#xff0c;今年 Bitcoin 生态迎来全新的发展活力和机遇。Ordinals 协议的横空出世&#xff0c;以此为基础诞生的 BRC20 协议给整个比特币生态带去了一波新的能量&#xff0c;迎来铭文热度高涨。而诸如 BitVM、BitStream 等新技术甫一问世&#xff0c;便引发…

学习分布式事务Seata看这一篇就够了,建议收藏

一、事务的特性 ACID特性 A&#xff08;Atomic&#xff09;&#xff1a;原子性&#xff0c;构成事务的所有操作&#xff0c;要么都执行完成&#xff0c;要么全部不执行&#xff0c;不可能出现部分成功部分失败的情况。 C&#xff08;Consistency&#xff09;&#xff1a;一致…

Python编程技巧 – Lambda函数

Python编程技巧 – Lambda函数 Python Programming Skills – Lambda Functions By JacksonML 2023-11-25 在前文介绍过Python函数&#xff0c;一个函数用def关键字声明&#xff0c;不带或带有参数&#xff0c;并以冒号结束&#xff1b;函数块根据结果由解释器确定返回值动态…

计数问题+约瑟夫问题(map)

目录 一、计数问题 二、约瑟夫问题 一、计数问题 #include<iostream> #include<map> using namespace std; int main() {int n,x;cin>>n>>x;map<int,int>m;for(int i1;i<n;i){if(i>1 && i<10){m[i];}else{int temp i;while (…

MYSQL基础知识之【添加数据,查询数据】

文章目录 前言MySQL 插入数据通过命令提示窗口插入数据使用PHP脚本插入数据 MySQL 查询数据通过命令提示符获取数据使用PHP脚本来获取数据内存释放 后言 前言 hello world欢迎来到前端的新世界 &#x1f61c;当前文章系列专栏&#xff1a;Mysql &#x1f431;‍&#x1f453;博…

【Jenkins】jenkins发送邮件报错:Not sent to the following valid addresses:

jenkins报错未能发送邮件到指定邮箱 注意&#xff1a;这是在系统配置中修改 在系统配置》邮件通知中添加配置信息 注意&#xff1a;这个是在项目的配置下修改 配置完成后&#xff0c;重新执行发送邮件成功&#xff01;&#xff01;&#xff01;

Python实现一箭穿心

文章目录 &#x1f384;效果&#x1f3f3;️‍&#x1f308;Turtle模块&#x1f339;代码&#x1f33a;代码讲解 &#x1f384;效果 &#x1f3f3;️‍&#x1f308;Turtle模块 Turtle是一个绘图工具&#xff0c;是Python标准库中的一个模块。它提供了一种简单而直观的方式来创…

在JVM中 判定哪些对象是垃圾?

目录 垃圾的条件 1、引用计数法 2、可达性分析 3、强引用 4、软引用 5、弱引用 6、虚引用 判断垃圾的条件 在Java虚拟机&#xff08;JVM&#xff09;中&#xff0c;垃圾收集器负责管理内存&#xff0c;其中的垃圾收集算法用于确定哪些对象是垃圾&#xff0c;可以被回收…

Linux的基本指令(二)

目录 前言 学前补充 touch指令 mkdir指令 rmdir指令 rm指令 通配符* man指令 cp指令 mv指令(重要) 补充内容&#xff1a; 1、如何快速在Linux中写出代码 2、如何看待如此多的Linux指令 cat指令 前言 关于Linux的基本指令我们会分三到四篇文章进行分析&#xff0c…

2.前端--HTML标签1【2023.11.25】

1.基本语法规范 HTML 标签是由尖括号包围的关键词&#xff0c;例如 <html>。HTML 标签通常是成对出现的&#xff0c;例如 和 &#xff0c;我们称为双标签。有些特殊的标签必须是单个标签&#xff08;极少情况&#xff09;&#xff0c;例如 <br />我们称为单标签。 …

持续增长的背后,艾比森用泛微-千里聆RPA机器人为业务加速

&#xff08;艾比森全球总部&#xff09; 艾比森集团始创于2001年&#xff0c;是全球知名的至真LED显示应用与服务提供商。目前旗下设有深圳总部&#xff0c;艾比森东江智造中心&#xff0c;以及艾比森美国、德国、日本、迪拜、俄罗斯、墨西哥、巴西、中国香港等18家海内外公司…

如何打造“面向体验”的音视频能力——对话火山引擎王悦

编者按&#xff1a;随着全行业视频化的演进&#xff0c;我们置身于一个充满创新与变革的时代。在这个数字化的浪潮中&#xff0c;视频已经不再只是传递信息的媒介&#xff0c;更是重塑了我们的交互方式和体验感知。作为字节跳动的“能力溢出”&#xff0c;火山引擎正在飞速奔跑…

openpnp - 丝杠安装调整的总结

文章目录 openpnp - 丝杠安装调整的总结概述笔记发现的问题 - X轴有回差发现的问题 - 丝杠两边的轴承座中心有高差(不同轴)备注END openpnp - 丝杠安装调整的总结 概述 设备定制回来后, 丝杠没敢动(开始是小白, 也没发现丝杠的相关问题. 因为看到同学自己安装丝杠那么痛苦, 最…

P12 C++静态关键字static

目录 01 前言 02 静态变量static 03 extern关键字 04 静态函数 最后的话 01 前言 static 关键字在 C 中有两个意思&#xff0c;这个取决于上下文。 第一种情况是在类或结构体外部使用 static 关键字&#xff0c;另一种是在类或者结构体内部使用 static。 类外面的 static…

【DP】mobiusp正在创作乐曲

输入样例1&#xff1a; 5 2 1 7 7 1 3 输出样例1&#xff1a; 2 输入样例2&#xff1a; 10 3 2 5 6 4 4 5 7 3 5 6 输出样例2&#xff1a; 1 #include<iostream> #include<cstring> #include<algorithm> #include<vector> using namespace std; typede…

jpom学习

jpom学习 整理jpom 一键安装 部署会需要 mvn跟jdk环境 # 安装服务端和 jdk、maven 环境 yum install -y wget && \ wget -O install.sh https://jpom.top/docs/install.sh && \ bash install.sh Server jdkmvndocker安装 安装docker挂载方式安装 docker …

【Docker】Docker 仓库管理和Docker Dockerfile

作者简介&#xff1a; 辭七七&#xff0c;目前大二&#xff0c;正在学习C/C&#xff0c;Java&#xff0c;Python等 作者主页&#xff1a; 七七的个人主页 文章收录专栏&#xff1a; 七七的闲谈 欢迎大家点赞 &#x1f44d; 收藏 ⭐ 加关注哦&#xff01;&#x1f496;&#x1f…

php订单发起退款(余额和微信支付)

index.html <a class="btn btn-danger btn-change btn-tuikuan btn-disabled" href="javascript:;"><i class="fa fa-tuikuan"></i> 订单退款</a>-->order.js // 为表格绑定事件Table.api.bindevent(table);//退款…