C++ 构造函数-2

news2024/9/21 22:20:28

构造函数-2

 构造函数体赋值

 在对象创建的时候,编译器会调用构造函数,给对象当中的成员赋一个合适的初始值。

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

但是其实本质上上述的操作并不是给成员初始化,只能说是,给成员赋值,整整的初始化只能初始化一次,而在构造函数体内能给成员多次赋值。

所以这时候我们就在构造函数中使用初始化列表

初始化列表

 初始化列表:以一个冒号开始,数据成员之间用 逗号进行分割,其中的每个"成员变量"后面跟
一个放在括号中的初始值或表达式。

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

如上图所示,数据成员后面的括号当中就是给这个数据成员初始化值。 

 看似这个初始化和 赋初值,看似差不多,但是在有些情况下,初始化列表才能解决。

【注意】:

  • 每一个成员最多只能再初始化链表当中出现一次
  • 有些成员必须在初始化列表初始化

上述必须初始化的成员有 :

  • 引用成员变量
  • const成员变量
  • 自定义成员类型(当其类中没有默认构造函数)

 比如我们直接使用构造中 直接赋初值,来初始化这个 const 的成员:

 像上述就报了,必须初始化常量限定····这个错,这就代表着要在 定义的时候初始化,那么我们在赋值的时候是不行的。

 那么我们就理解实质了,也就是说,如我们不使用初始化列表,那么我们在构造函数中的初始化就是直接 赋初值,那么这种方式是不能初始化 在定义的时候就需要初始化的成员的。

  也就是说,在C++当中,这个位置不认为是初始化的地方:

 如上图,这个只是赋值。

 在这个位置才认为是 初始化。

我们之前说过,当我们初始化一个成员的时候,如果是内置类型,不进行处理:

 但是也不是都不处理,如果这个内置类型,在类当中是定义给了值的:

 这个不是初始值,这个是 给 初始化列表的缺省值,也就是说,如果我们不对这个内置类型进行处理,那么这个x 就会被 赋 1 (缺省值)这个值,如果我们在初始化列表当中对这个x 进行了赋值,那么这个x 就是 在初始化列表当中初始化的值。

 

 

 同样,如果对象是 没有默认构造函数,那么我们在创建这个对象的时候,就会报错:

 如上图,我们在B类当中创建一个了一个 A类的对象,但是如下图所示,(没有缺省参数)而我们创建的是无参的构造函数来创建这个对象的,我们构造函数当中没有默认构造函数,那么就会报错:

 

 当我们在A 的构造函数中 的 初始化列表当中去的 调用这个对象的构造函数,去创建这个对象,那么就不会报错:

 而且我们发现,成功赋值。

所以我们建议我们能使用初始化列表,就用初始化列表,因为不管你是否使用初始化列表,对于自定义类型成员变量,一定会先使用初始化列表来进行初始化,因为初始化列表是这个些成员定义的地方,但是也不是意味着所有的初始化都可以用初始化列表。

class Stack
{
public:
	Stack(int Top, int capacity)
		:_a((int*)malloc(sizeof(int)))
		,Top(Top)
		,capacity(capacity)
	{
		if (_a == NULL)
		{
			perror("malloc fail");
			exit(-1);
		}

		// 初始化数组
		memset(_a, 0, sizeof(int) * capacity);
	}

protected:
	int* _a;
	int Top;
	int capacity;
};

比如上述例子,我们在实现栈的构造函数的时候,我们希望在 初始化列表当中去 初始化 _a 指针指向一块空间。那么我们是可以实现的。

但是,我们知道,malloc函数是有可能会开辟空间失败的,所以我们要进行判断,但是这个判断在是在 初始化列表当中不能做到的,我们就只能再的 构造函数中实现,那既然有这样的场景,那么假设我们还想初始化我们创建的数组,那么我们还需要再使用这个函数来进行处理,如上述例子一样。

这些都是初始化列表不能实现的,而且,这种情况很多时候不止一种,可能会有很多行,我们这里想表达的意思是,总有一些工作时初始化列表做不完的。那么我们就可以在函数体当中去实现。

再比如我们要动态开辟二维数组,那其中必然就有一个循环,这也是初始化列表不能实现的。

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

 如下例子:
 

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 ,而是 1 随机值。

 因为上述例子,我们是声明的 _a2  这个变量,那他就会先初始化,那么我们在初始化列表当中写的是 _a2(_a1) 此时 _a1 还没有会被初始化,所以是随机值。先走  _a2(_a1) 再走 _a1(a)。

 上述例子还不是很严重,我们在之前实现栈的构造函数的时候,如果想下述这样写就会出错了:

 

 我们发现上述在初始化 _a  的时候,capacity是没有初始化的,也就是说现在 的 capacity  是一个随机值,那么我们以一个随机值来开辟空间大小,就会出现很严重的问题。

 所以,我们一般把声明的顺序和初始化的顺序保持一致。

 explicit关键字 

 隐式类型转换

 例子:

	B b(10);   //1
	B c = 10;  //2

 代码1是在调用 B 这个类的构造函数来创建这个对象,那么 代码2是在干嘛呢?

我们发现他是一个隐式的类型转换,由整形类型转化成自定义类型。

他是先用 10 ,利用 B 的构造函数来创建一个 临时对象,这个临时对象的类型就是 自定义类型B,然后 c 再用 B 当中的拷贝构造函数去 把 临时对象,拷贝给 c 这个对象。

 我们发现,上述即调用了 构造函数,还调用了 拷贝构造函数,但是编译器不想这样做,他不像上面这个代码,即调用构造函数有调用拷贝构造函数,所以,一遍像上述情况,编译器会进行优化。

 像上述例子,他会直接使用 10 这个整型数据来进行 构造。

 我们把 B的拷贝构造写出来,其中加入打印,看看构造函数和 拷贝构造函数是否被调用:

class B
{
public:
	B(int n)
		:_n(n)
		//,aobj(10)
	{
		cout << "B::B(int)" << endl;
	}

	B(const B& b)
		:_n(b._n)
	{
		cout << "B::B(const B& b)" << endl;
	} 

protected:
	const int _n;
};

int main()
{
    B b(10);   //1
	B c = 10;  //2

    return 0;
}

 输出:

 我们发现,拷贝构造函数并没有被调用,此处就是被编译器优化了,如果按照本来的实现过程,应该还有临时对象的创建。

 构造函数和 拷贝构造函数都是 构造函数,除了老的编译器,现在的编译器一般都不会容忍在同一个表达式当中重复的调用构造函数。

 我们现在来举一个反例,来验证我们刚刚说个,创建临时对象这一过程:

	B& pb1 = 2;   //代码1

 pb1 去引用之前的 b 和 c  都是可以的 ,但是,我们上述引用的是一个整形 2 ,这就不行了,报错:

 但是如果我们把这个引用转换成 const 的就可以了:

	const B& pb2 = 2;   //代码2

 我们发现上述代码编译通过了。

我们上述定义的是 B 类型的引用类型,编译器在这时候就不能在进行优化了,上述代码的实现过程就是我们之前说的,创建一个 临时对象来进行 赋值,像上述的代码2,就是用2 ,调用构造函数创建了一个 临时对象,这里的 pb2 引用的就是 这个临时对象。

而临时对象具有常性,所以,之前的报错是 也 我们的引用类型不是 const 所修饰的引用,当我们用const 修饰之后就可以编译通过了。如下图所示:

 此时我们运行这个代码,输出:

 我们发现只调用了一次构造函数。

那么像上述的这种创建一个临时对象来让一个自定义类型,接收一个不是这个类型的值,然后来创建这个对象,主要是为了实现像下述代码这种情况:

class list
{
public:
	list(const string& st)
	{
	}
};

int main()
{
	string name1("李四");
	list LS1(name1);     // 代码1
	list LS2("李四");    // 代码2

	return 0;
}

 上述代码 1  和 代码 2 ,肯定是 代码2 的方式比较方便,代码能实现这种情况的是因为,“李四” 这个字符串就和 const String& 之间就发生了 隐式类型转换,和之前的 const B& pb2 = 2; 是一样的。

当我们 不加 const修饰就报错了:

 explicit关键字 

 根据上述的 list 类的描述,我们知道:构造函数不仅可以构造与初始化对象,对于单个参数或者除第一个参数无默认值其余均有默认值的构造函数,还具有类型转换的作用

 那么我们在自定义类型,实现构造函数的时候,我们就可以在函数名之前加一个 explicit 这个关键字,来防止类似于 B b = 10 这样的 隐式类型转换:

 Static 成员

假设我们现在想要,记录当前我们实现的类,到底创建了多少个对象,那么我们可以在 全局当中定义一个变量(scount),用这个变量来计算我们创建了多少个 对象;当我们调用这个类当中的 构造函数或者是拷贝构造函数的时候,我们就 scount++;如果我们调用的是 析构函数就 scount--。从而来记录,当前有多少个这个类的对象。

static int _scount = 0;

class A
{
public:
	A() { ++_scount; }

	A(const A& t) { ++_scount; }

	~A() { --_scount; }
};


void func(A aa)
{
	cout << __LINE__ << " : " << _scount << endl;
	return;
}

int main()
{
	cout << __LINE__ << " : " << _scount << endl;
	A aa1;
	A aa2;
	func(aa2);
	cout << __LINE__ << " : " << _scount << endl;
}

 输出:

 我们上述使用  __LINE__ 这个宏来打印当前行数,上述就输出了 每一行代码的情况下当前有多少个对象。

当然,不管你在  局部,全局,还是在 对象当中去创建这个对象,只要是创建对象都需要构造函数或者是 拷贝构造函数,那么只要调用,计数器就会++;只要调用析构函数,计数器都会 --。

如上述,我们在函数中创建的 对象,在对象当中打印这个 计数器,发现是3,说明当前已经创建这个局部的对象,但是在函数调用结束之后,对象会调用析构函数进行销毁,那么计数器就会--,所以我们在主函数当中打印的计数器的值是 2。

但是,我们上述使用的全局变量,这样做不太好,所以我们在定义在定义类型的时候,可以对类似这样需要全局实现的 变量,进行封装,把这边变量封装到 自定义类型当中。

Static 成员

 声明static的类成员称为类的静态成员,用static修饰的成员变量,称之为静态成员变量;用
static修饰的成员函数,称之为静态成员函数静态成员变量一定要在类外进行初始化。

 这里我们要搞清楚,普通成员变量和 静态成员变量,这两个的区别,普通成员属于类的当前对象,而 静态成员变量属于的是类的每一个对象,因为是静态的,他的生命周期是全局的;而普通成员的生命周期是 这个对象的生命周期。

 因为 静态成员变量是属于全局的,所以我们在 对象当中,利用构造函数去初始化是不行的:

 如上图,报错了。

静态成员变量只能再 全局位置 ,类外面进行 定义。像下面这样定义这个静态成员变量。

int A::_scount = 0;

那么像我们之前实现的 _scount 是 protected 的,那么我们就不能直接 A::_scount 这样访问了,除非这个 _scount 是 public 的。

 当然为了封装,我们一般不会是 共有的,一般是私有的,public的和 全局的每什么区别,达不到封装的效果。

所以为了取到 不是 public静态成员变量,我们就创建一个 静态成员函数,来取到 其中的 静态成员变量。

 静态成员函数没有隐藏的this指针,不能访问任何非静态成员

	static int Get_Scount()
	{
		return _scount;
	}

因为这个静态成员函数不能访问非静态的成员,他能帮我们取到 静态的成员的值,所以我们使用这个函数来取到数据,又不会破坏封装性。

class A
{
public:
	A() 
	{ 
		++_scount;
	}

	A(const A& t) { ++_scount; }

	~A() { --_scount; }

	static int Get_Scount()
	{
		return _scount;
	}

protected:
	static int _scount;
};

int A::_scount = 0;


void func(A aa)
{
	cout << __LINE__ << " : " << A::Get_Scount() << endl;
	return;
}

int main()
{
	cout << __LINE__ << " : " << A::Get_Scount() << endl;
	A aa1;
	A aa2;
	func(aa2);
	cout << __LINE__ << " : " << A::Get_Scount() << endl;
}

我们可以通过 " . "  或者是 " 类名:: "  来访问静态成员函数。 

静态成员变量是不能给缺省值的,因为静态成员变量的初始化不在 初始化列表当中,而是在 全局当中。 

关于静态和非静态的关系:
同类中 非静态可以调用非静态,而静态 只要是不受指定类域,和访问限定符的限定就可以访问。

 

 因为 访问非静态的成员函数的访问,需要 this 指针,而在静态成员函数中没有 this 指针。

设计一个类只能在 栈/堆上创建对象

如下,在C++当中的对象存储的位置:

class A
{
public:

protected:
	int _a = 1;
	int _b = 1;
};

int main()
{
	static A aa1;   // 静态区
	A aa2;			// 栈
	A* ptr = new A; // 堆

	return 0;
}

我们发现,我们创建对象的地方有好几个,而且像上述一样实现的类,用户可以在 静态区 栈 堆 上随便选个位置来创建对象。

我们可以把 构造函数 的访问权限 设置为 protected 私有的 ,这样我们在类外部就不能随便去调用这个构造函数,去创建对象。
那么问题来了,既然是私有的,那么我们在类外部怎样去 调用这个 构造函数 呢?

我们可以定义 public 的 成员函数,既然在来外面不能调用,那么我们就在 类当中去调用构造函数,如下所示:

 这样我们可以通过调用这些函数 来在对应的 位置创建对象。

但是,向上述还是有问题,既然这个函数是成员函数,那么我们如果在类外面访问这个函数呢?

我们如果想访问成员函数必须通过对象来访问,但是现在,我们不能创建对象。

这时候,静态的成员函数就可以帮我们解决这个问题,因为静态的成员函数就可以在类外面进行调用,他是全局的,不在类当中。

class A
{
public:
	static A GetStackA()
	{
		A aa;
		return aa;
	}

	static A* GetHeapA()
	{
		return new A;
	}

protected:
	int _a = 1;
	int _b = 1;
};

如上述这种,我们就可以只创建对应的函数,来达到只能再某一个区域创建对象的这种限制。

 例题:

求1+2+3+...+n,要求不能使用乘除法、for、while、if、else、switch、case等关键字及条件判断语句(A?B:C)。

这里就可以利用这个 静态成员,通过调用n次构造函数来,计算出从1 加到 n 的值。这里我们想到创建一个这个类的数组,数组元素个数为 n。

class Sum
{
public:
    Sum()
    {
        _sum += _i;
        _i++;
    }

    int GetSum()
    {
        return _sum;
    }

protected:
    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 a[0].GetSum();
    }
};

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

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

相关文章

Android framework工程师一定要知道的高级技巧

作为一名android framework工程师&#xff0c;你是否对Android framework的一些高阶使用技巧感到陌生&#xff1f;想了解更多的细节&#xff1f;如果是这样&#xff0c;那么就请你读下去。本篇文章我将为大家相信介绍Android framework的高阶技巧&#xff0c;帮助你成为一名高级…

【Nginx】【SSL】Nginx上配置ssl证书

配置需要有自己的域名和云主机&#xff1b;域名已经解析到主机&#xff1b;安装好Nginx 一、申请免费版的SSL证书 1、阿里云可以申请免费版的SSL证书 阿里云搜索 ssl 找到 数字证书管理服务/SSL 证书>免费证书&#xff1b;申请一个免费的 2、下载SSL证书到本地&#xf…

贝叶斯公式与全概率公式的理解。

1.贝叶斯与全概率公式解释 1.全概率公式定义 即若在某个场景下&#xff0c;可找到一个完备事件组 Ai ( i 1,2,3…n)。 则对任一与该场景有关的事件 B&#xff0c;都可以分割成无数个小事件&#xff08;由不同因素引起的事件&#xff09; 有&#xff1a;   B B ∩ A1 ∪ A2…

给你的项目启动提提速:Lazy Initialization

前言 在一个名为种花家的小镇上&#xff0c;生活着一群热爱编程的人。他们致力于构建出高效、可维护的软件系统&#xff0c;而 Spring Boot 框架成为了他们的不二之选。这个小镇上的人们每天都在用 Spring Boot 框架创造着令人瞩目的应用程序。 然而&#xff0c;随着时间的推移…

Java 学习之线程

1、引入线程的优点&#xff1a; 1&#xff09;充分利用cup资源 2&#xff09;简化编程模型 3&#xff09;简化异步事件处理 4&#xff09;使GUI更有效率 5&#xff09;节约成本 2、线程使用&#xff1a;在Java中创建线程有几种方法&#xff0c;每个Java程序至少包含一个线…

软件工程开发文档写作教程(05)—可行性研究报告写作规范

本文原创作者&#xff1a;谷哥的小弟作者博客地址&#xff1a;http://blog.csdn.net/lfdfhl本文参考资料&#xff1a;电子工业出版社《软件文档写作教程》 马平&#xff0c;黄冬梅编著 软件工程开发文档现状 一个软件项目从立项到结尾共有几个阶段&#xff1a;立项&#xff0c;…

动画图解常见串行通讯协议:SPI、I²C、UART、红外分析

一、SPI传输 图1&#xff1a;SPI 数据传输 图1.2&#xff1a;SPI数据传输&#xff08;2&#xff09; ​ 图1.3&#xff1a; SPI时序信号 二、IC传输 图1.2.1&#xff1a; I2C总线以及寻址方式 三、UART传输 图1.3.1&#xff1a;PC 上通过UART来调试MCU 图1.3.2&#xff1a;R…

深入探究语音识别技术:原理、应用与实现

❤️觉得内容不错的话&#xff0c;欢迎点赞收藏加关注&#x1f60a;&#x1f60a;&#x1f60a;&#xff0c;后续会继续输入更多优质内容❤️ &#x1f449;有问题欢迎大家加关注私戳或者评论&#xff08;包括但不限于NLP算法相关&#xff0c;linux学习相关&#xff0c;读研读博…

Qt-AES加密库

Qt-AES加密库 AES在线加解密工具[1] Qt-AES加密库[2] Qt AES/DES加密算法库 软件/文件/任意长度字符串加密 试用期许可使用方法软件试用期算法对称加密和非对称加密非对称加密&#xff08;Asymmetric Cryptography&#xff09;实例总结加密算法 [3] Qt笔记-AES加密[4] AES 加密…

【Betternet怎么用呢?】Betternet下载使用完整教程

Betternet是一款非常历史悠久的访问世界互联网行业的工具了。知道Betternet的用户&#xff0c;也应该是比较久的互联网用户了。早在2015年左右&#xff0c;那时候的betternet就是很多外贸行业的朋友上gmail以及Facebook上开发客户必备的工具了。 因为那时候betternet使用简单&…

Pandas + ChatGPT 超强组合,pandas-ai :交互式数据分析和处理新方法

Python Pandas是一个为Python编程提供数据操作和分析功能的开源工具包。这个库已经成为数据科学家和分析师的必备工具。它提供了一种有效的方法来管理结构化数据(Series和DataFrame)。 在人工智能领域&#xff0c;Pandas经常用于机器学习和深度学习过程的预处理步骤。Pandas通过…

第7章链接:静态链接、符号表、符号解析

文章目录 7.2 静态连接7.3 目标文件7.4 可重定位目标文件7.5 符号和符号表7.6 符号解析7.6.1 链接器如何解析多处定义的全局符号7.6.2 与静态库链接7.6.3 链接器如何使用静态库来解析引用 7.2 静态连接 像 Unix ld 程序这样的静态链接器&#xff08;static linker&#xff09;…

C语言函数大全-- u 开头的函数

C语言函数大全 本篇介绍C语言函数大全-- u 开头的函数 1. ultoa 1.1 函数说明 函数声明函数功能char *ultoa(unsigned long value, char *str, int base);用于将无符号长整型数转换成指定基数下的字符串表示 参数&#xff1a; value &#xff1a; 要转换的无符号长整型数st…

docker容器无法执行vim【已解决】

docker容器无法执行vim【已解决】 docker容器中执行vim失败安装文件没更换之前&#xff0c;速度非常的慢【失败】这里我更换了163的但是报错【失败】这里我更换了阿里的第一种报错【成功】&#xff1a;&#xff1a;&#xff1a;&#xff1a;这里我更换了阿里的第二种成功 完整步…

struct模块进行数据打包

原理&#xff1a; 将一组简单数据进行打包&#xff0c;转换为bytes格式发送。或者将一组bytes格式数据&#xff0c;进行解析。 接口使用 Struct(fmt) 功能: 生成结构化对象 参数&#xff1a;fmt 定制的数据结构 st.pack(v1,v2,v3…) 功能: 将一组数据按照指定格式打包转换为by…

分子动力学基础知识

分子动力学基础知识 目前主要存在两种基本模型&#xff1a;其一为量子统计力学, 其二为经典统计力学。 量子统计力学 基于量子力学原理, 适用 于微观的, 小尺度, 短时 间的模拟&#xff0c;可以描述电子 的结构分布&#xff0c;原子间的成 键断键等化学性质。 经典纭计力学…

MySQL原理(七):内存管理和磁盘管理

前言 上一篇介绍了 MySQL 的日志&#xff0c;这一篇将介绍内存管理和磁盘管理相关的内容。 内存管理 MySQL 的数据都是存在磁盘中的&#xff0c;我们要更新一条记录的时候&#xff0c;得先要从磁盘读取该记录&#xff0c;然后在内存中修改这条记录。修改完这条记录后会缓存起…

15 KVM虚拟机配置-体系架构相关配置

文章目录 15 KVM虚拟机配置-体系架构相关配置15.1 概述15.2 元素介绍15.3 AArch64架构配置示例15.4 x86_64架构配置示例 15 KVM虚拟机配置-体系架构相关配置 15.1 概述 XML中还有一部分体系架构相关的配置&#xff0c;这部分配置包括主板&#xff0c;CPU&#xff0c;一些与体…

【2023/05/10】Mitchel Resnick

Hello&#xff01;大家好&#xff0c;我是霜淮子&#xff0c;2023倒计时第5天。 Share Her wistful face haunts my dreams like the rain at night. 译文&#xff1a; 她的热切的脸&#xff0c;如夜雨似的&#xff0c;搅扰着我的梦魂。 Once we dreamt that we were stra…

论文解读:DELPHI:用于蛋白质相互作用位点预测的精确深度集成模型

期刊&#xff1a; Briefings in Bioinformatics 出版日期 2022-11-22 websever:https://iasri-sg.icar.gov.in/pldbpred/ 网址&#xff1a; PlDBPred: a novel computational model for discovery of DNA binding proteins in plants | Briefings in Bioinformatics | Oxfo…