【C++入门】类的6个默认成员函数、运算符重载、初始化列表、const成员、static成员

news2024/11/20 12:40:08

目录

引言

构造函数

引入构造函数

构造函数的特征

一些细节 

析构函数

析构函数的特性

注意事项 

拷贝构造函数

书写格式

使用细节

拷贝构造的典型应用场景 

运算符重载

意义与格式

注意事项 

赋值运算符重载

const成员

两个经典问题

再谈构造函数—初始化列表

注意事项 

static成员

概念

特性 

两个问题 


引言

一个类中什么都没有,简称为空类

空类中真的什么都没有吗?并不是的,任何类在什么都不写时,编译器会自动生成以下6个默认成员函数

默认成员函数:用户没有显示实现,编译器会自动生成的函数叫默认成员函数。

构造函数

引入构造函数

下面通过一段代码引入:

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

	void Print()
	{
		cout << _year << "年" << _month << "月" << _day << "日" << endl;
	}

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

int main()
{
	Date d1;
	d1.Init(2024, 4, 6);
	d1.Print();

	Date d2;
	d2.Init(2024, 4, 5);
	d2.Print();

	return 0;
}

对于Date类,我们可以通过公有函数Init来给对象设置日期,但如果每次创建对象时都要调用该函数来设置日期,未免有点麻烦。有没有一种方法,在对象创建时,就将信息设置进去呢?构造函数就是为解决此问题而生的。

构造函数是一个特殊的成员函数,名字与类名相同,在创建类类型的对象时,编译器自动调用,以保证每个数据都有一个合适的初始值,并且在对象的整个生命周期内只调用一次。

需要注意的是,构造函数虽然名称是构造,但是它的主要任务并不是开空间创建对象,而是初始化对象。

构造函数的特征

> 函数名与类名相同

> 无返回值

> 对象实例化时编译器自动调用对应的构造函数

> 构造函数可以重载

示例:

class Date
{
public:
	//无参构造函数
	Date()
	{}

	//带参构造函数
	Date(int year, int month, int day)
	{
		_year = year;
		_month = month;
		_day = day;
	}
private:
	int _year;
	int _month;
	int _day;
};

一些细节 

如果我们没有显示的写构造函数,那么编译器会自动生成一个默认构造函数,一旦我们显示的定义了,编译器将不会在自动生成。

不写默认构造:

class Date
{
public:

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

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

int main()
{
	Date d1;
	d1.Print();

	return 0;
}

运行结果是:

输出的均为随机值。

 显式写了构造函数,但不是不用传参就能调用的,也会报错,请看下面的代码:

class Date
{
public:

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

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

int main()
{
	Date d1;

	return 0;
}

 

内置类型成员在类中声明时可以给默认值(缺省值)。

class Date
{
public:

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

	void Print()
	{
		cout << _year << "年" << _month <<"月" <<  _day << "日" << endl;
	}

private:
	int _year = 1; //在类中声明时可以给缺省值
	int _month = 1;
	int _day = 1;
};

int main()
{
	Date d1(2024, 4, 18);
	d1.Print();

	return 0;
}

总结:

默认生成的构造函数,对内置类型成员不做处理。内置类型包括int、double及任意类型的指针等。

默认生成的构造函数,对自定义的类型成员,会调用默认构造函数(不用传参就能调用的那个函数)。 

内置类型成员在类中声明时可以给缺省值。

析构函数

与构造函数相反,析构函数是完成对象中资源的清理工作。对象在销毁时会自动调用析构函数

析构函数的特性

> 析构函数函数名在类型前面加上~

> 无参数无返回值

> 一个类只能有一个析构函数,若未显式定义,编译器会自动生成默认构造函数

> 析构函数不能重载

> 对象生命周期结束时,自动调用

> 对于内置类型,自动调用析构函数,对于内置类型不会,因为内置类型中没有资源要清理

注意事项 

如果类中没有申请资源,析构函数可以不写,直接使用编译器自动生成的析构函数,比如Date类,

如果涉及到资源的申请,就必须要写析构函数,清理相应资源,否则会导致内存泄漏。

拷贝构造函数

书写格式

只有单个形参,形参类型为类类型对象的引用(一般加const修饰),在用已存在的类类型对象创建新的对象时,编译器自动调用。

//错误写法
    Date(const Date d)
    {
        _year = d._year;
        _month = d._month;
        _day = d._day;
    }

//正确写法
    Date(const Date& d)
    {
        _year = d._year;
        _month = d._month;
        _day = d._day;
    }

第一种写法,编译时编译器报错。

因为采用传值方式,会导致无穷递归。 正确的做法应该是要传引用

还需注意,不要将拷贝构造函数和构造函数混淆,通过它们的参数列表可以很好区分。

使用细节

对于内置类型,可以直接使用编译器自动生成的拷贝构造,编译器生成的拷贝构造函数是按字节序完成拷贝的,这种拷贝方式叫做浅拷贝,或者值拷贝

对于自定义类型,需要调用自己写的拷贝构造,原因如下。

请看下面的例子: 

typedef int DataType;
class Stack
{
public:
	Stack(size_t capacity = 10)
	{
		_array = (DataType*)malloc(capacity * sizeof(DataType));
		if (nullptr == _array)
		{
			perror("malloc申请空间失败");
			return;
		}
		
		_size = 0;
		_capacity = capacity;
	}

	void Push(const DataType& data)
	{
		_array[_size] = data;
		_size++;
	}

	~Stack()
	{
		if (_array)
		{
			free(_array);
			_array = nullptr;
			_capacity = 0;
			_size = 0;
		}
	}
private:
	DataType* _array;
	size_t _size;
	size_t _capacity;
};

int main()
{
	Stack s1;
	s1.Push(1);
	s1.Push(2);
	s1.Push(3);
	s1.Push(4);

	Stack s2(s1);
	return 0;
}

总结:

如果没有涉及资源的申请,拷贝构造函数是否写都可以,但是,一旦涉及到资源的申请,就必须写拷贝构造函数,否则就是浅拷贝。 

拷贝构造的典型应用场景 

> 使用已存在对像创建新对象

> 函数参数类型为类类型对象

> 函数返回值类型为类类型对象

class Date
{
public:
	Date(int year, int minute, int day)
	{
		cout << "Date(int,int,int):" << this << endl;
	}

	Date(const Date& d)
	{
		cout << "Date(const Date& d):" << this << endl;
	}

	~Date()
	{
		cout << "~Date():" << this << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};

Date Test(Date d)
{
	Date temp(d);
	return temp;
}

int main()
{
	Date d1(2024, 4, 19);
	Test(d1);
	return 0;
}

自己实现了析构函数释放空间,就需要实现拷贝构造函数。 

运算符重载

意义与格式

C++ 为了增强代码的可读性引入了运算符重载 运算符重载是具有特殊函数名的函数 ,也具有其返回值类型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似

关键字:operator

格式:返回值类型 operator运算符(参数列表)

注意事项 

> 不能通过连接其他符号来创建新的操作符:比如 operator@
> 重载操作符必须有一个类类型参数
> 用于内置类型的运算符,其含义不能改变,例如:内置的整型 + ,不能改变其含义
> 作为类成员函数重载时,其形参看起来比操作数数目少 1 ,因为成员函数的第一个参数为隐藏的this
> .*  ::  sizeof  ?:  .   注意以上 5 个运算符不能重载。

赋值运算符重载

> 参数类型:const T&  , 传引用可以提高传参效率。

> 返回值类型: T& , 返回引用可以提高效率,有返回值的目的是为了支持连续赋值。

> 检查是否自己给自己赋值,应避免这种情况发生,因为会降低程序的效率。

> 返回的是 *this,这一点在实现的时候自然就明白了。

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

	Date(const Date& d)
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}
    
    //赋值运算符重载
	Date& operator=(const Date& d)
	{
		if (this != &d)
		{
			_year = d._year;
			_month = d._month;
			_day = d._day;
		}
		return *this;
	}
	
private:
	int _year;
	int _month;
	int _day;
};

int main()
{
	Date d1(2024, 4, 19);
	Date d2;

	d1 = d2;

	return 0;
}

 能不能把赋值重载函数定义在全局呢?

请看代码:

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

	Date(const Date& d)
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}

	/*Date& operator=(const Date& d)
	{
		if (this != &d)
		{
			_year = d._year;
			_month = d._month;
			_day = d._day;
		}
		return *this;
	}*/
	
//private:
	int _year;
	int _month;
	int _day;
};

//全局
Date& operator=(Date& d1, Date& d2)
{
	if (&d1 != &d2)
	{
		d1._year = d2._year;
		d1._month = d2._month;
		d1._day = d2._day;
	}
	return d1;
}

int main()
{
	Date d1(2024, 4, 19);
	Date d2;

	d1 = d2;

	return 0;
}

运行结果:

结论:

赋值运算符重载函数必须是类的成员函数。

原因:赋值重载函数是一个默认成员函数,如果在类中未显示定义,那么编译器会自动生成一个(逐字节拷贝),这就会与我们定义在全局的赋值重载函数冲突,所以赋值运算符重载函数必须为类的成员函数。

 和拷贝构造函数一样,如果没有涉及资源管理,那么赋值重载函数是否实现都可以,一旦涉及到资源管理,必须实现赋值重载函数。

const成员

将const修饰的成员函数称为“const成员函数”,const实际上修饰的是隐含的this指针。表明该成员函数中,不能对类的任何成员进行修改。

两个经典问题

1. const 对象可以调用非 const 成员函数吗?
2. const 对象可以调用 const 成员函数吗?
下面依次解答。
1. const 对象可以调用非 const 成员函数吗?
不可以,const对象中的成员是不可以修改的,而非const对象中的成员是可以修改的,这是典型的 权限放大 ,是不允许的。

 2. const对象可以调用const成员函数吗?

可以, 非const对象调用const成员函数是一种权限缩小的行为,是允许的。

再谈构造函数—初始化列表

通过一段简单代码引入:

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

在对象实例化时通过上述的构造函数后,类的成员变量有了一个初值,但这并不是初始化,严格来说,应该是赋值,因为在函数体内,可以给同一变量多次赋值,但每一个变量的初始化只有一次,所以,在函数体内给值根本不叫初始化。类的成员变量真正初始化的地方在初始化列表。

初始化列表是构造函数的一部分,由冒号开始,逗号分割,每个成员变量后跟着的括号放初始值或表达式。

    Date(int year = 1970, int month = 1, int day = 1)
        :_year(year)      //初始化列表
        ,_month(month)
        ,_day(day)
    {}

注意事项 

1)每个成员变量在初始化列表中只能出现一次(初始化只能初始化一次)

2)尽量使用初始化列表初始化。初始化列表是所有成员变量(static成员变量除外)定义和初始化的位置,不管是否显式在初始化列表写,都会走初始化列         表进行定义和初始化。如果在初始化列表中给初始值,那么就会用初始值来初始化,如果没给,那么将会用缺省值来初始化。

3)成员变量在初始化列表中的初始化顺序与声明的顺序一致,与初始化列表中的顺序无关。

 

static成员

概念

声明为static的类成员称为类的静态成员。类的静态成员包含两类:静态成员变量和静态成员函数。

用static修饰的成员变量叫做静态成员变量。

用static修饰的成员函数叫做静态成员函数。

静态成员变量必须在类外进行初始化。上面我们说过,初始化列表是用来给成员变量初始化的,具体的说,是用来给实例化出的对象中的变量初始化的,而静态成员为所有类对象共享,不属于某个具体对象,所以它不是在初始化列表初始化的。

class Date
{
public:
	Date(int year = 1970, int month = 1, int day = 1) 
		:_year(year)
		, _month(month)
		, _day(day) 
		{}
private:
	int _year;
	int _month;
	int _day;
	static int _m;//静态成员变量的声明
};

int Date::_m = 0;//静态成员变量的初始化

特性 

1)静态成员为所有类对象共享,不属于某个具体的对象,存放在静态区。

2)静态成员变必须在类外定义,在类外定义式不加关键字static,类中只是声明。

3)类的静态成员可以用类名::静态成员或对象.静态成员这两种方式来访问。

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

5)静态成员也是类的成员,受访问限定符的限制。

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

	static int Get_m()
	{
		return _m;
	}

private:
	int _year;
	int _month;
	int _day;
	static int _m;//静态成员变量的声明
};

int Date::_m = 0;//静态成员变量的初始化

int main()
{
	cout <<Date::Get_m() << endl;

	return 0;
}

 可以通过静态成员函数去访问静态成员变量。

两个问题 

1)静态成员函数可以调用非静态成员函数吗?

不可以直接调用。请看下面例子:

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

	int Add(int x, int y)
	{
		return x + y;
	}

	static int Get_m()
	{
		int ret = Add(1, 2);
		return _m;
	}

private:
	int _year;
	int _month;
	int _day;
	static int _m;//静态成员变量的声明
};

int Date::_m = 0;//静态成员变量的初始化

int main()
{
	cout <<Date::Get_m() << endl;

	return 0;
}

运行结果:

 

2)非静态成员函数可以调用静态成员函数吗?

非静态成员函数可以直接调用静态成员函数。

 


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

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

相关文章

『FPGA通信接口』汇总目录

Welcome 大家好&#xff0c;欢迎来到瑾芳玉洁的博客&#xff01; &#x1f611;励志开源分享诗和代码&#xff0c;三餐却无汤&#xff0c;顿顿都被噎。 &#x1f62d;有幸结识那个值得被认真、被珍惜、被捧在手掌心的女孩&#xff0c;不出意外被敷衍、被唾弃、被埋在了垃圾堆。…

使用http-parser解析http请求和响应数据

1 简介 http-parser是一个用C编写的HTTP消息解析器&#xff0c;专为高性能HTTP应用程序设计。它能够解析HTTP/1.0和HTTP/1.1的消息&#xff0c;包括头部、主体和连续行。当解析到特定的HTTP元素&#xff08;如请求行、头字段或消息体&#xff09;时&#xff0c;会触发相应的回调…

【前端面试3+1】15 CSS如隐藏元素、css块级元素和行内元素有哪些?两者有什么区别?、JavaScript中“==”与“===”的区别、【丢失的数字】

一、CSS如何隐藏元素&#xff1f; 1、使用 display: none; 这种方法会隐藏元素&#xff0c;并且不占据页面空间。元素会被完全移除&#xff0c;无法通过任何方式显示出来。 .hidden-element {display: none; }2、使用 visibility: hidden; 这种方法会隐藏元素&#xff0c;但仍然…

线段树汇总

线段树是一种二叉搜索树&#xff0c;与区间树相似&#xff0c;它将一个区间划分成一些单元区间&#xff0c;每个单元区间对应线段树中的一个叶结点。 使用线段树可以快速的查找某一个节点在若干条线段中出现的次数&#xff0c;时间复杂度为O(logN)。而未优化的空间复杂度为2N&a…

Office 2024安装教程(附免费安装包资源)

鼠标右击软件压缩包&#xff0c;选择“解压到Office 2024安装包”。 打开解压后的文件夹&#xff0c;鼠标右击“YAOCTRI_Installer”选择“以管理员身份运行”。 输入数字“1”自动开始安装。 软件正在安装&#xff0c;请耐心等待&#xff0c;谢谢。 安装完成&#xff0c;点击“…

浅析Redis④:字典dict实现

什么是dict&#xff1f; 在 Redis 中&#xff0c;dict 是指哈希表&#xff08;hash table&#xff09;的一种实现&#xff0c;用于存储键值对数据。dict 是 Redis 中非常常用的数据结构之一&#xff0c;用于实现 Redis 的键空间。 在 Redis 源码中&#xff0c;dict 是一个通用…

初学python记录:力扣39. 组合总和

题目&#xff1a; 给你一个 无重复元素 的整数数组 candidates 和一个目标整数 target &#xff0c;找出 candidates 中可以使数字和为目标数 target 的 所有 不同组合 &#xff0c;并以列表形式返回。你可以按 任意顺序 返回这些组合。 candidates 中的 同一个 数字可以 无限…

Electron 30.0.0 发布,升级 Node 和 V8 引擎

近日&#xff0c;Electron 30.0.0 正式发布&#xff01;你可以通过 npm install electronlatest 进行安装&#xff0c;或者从 Electron 的发布网站下载&#xff0c;继续阅读了解此版本的详细信息。 &#x1f525; 主要更新 Windows 上支持 ASAR 完整性融合。如果未正确配置&am…

【JAVA基础篇教学】第十一篇:Java中字符串操作详解

博主打算从0-1讲解下java基础教学&#xff0c;今天教学第十篇&#xff1a;Java中字符串操作详解。 在 Java 编程中&#xff0c;字符串是一种常见的数据类型&#xff0c;通常用于存储文本信息。Java 提供了丰富的字符串操作方法&#xff0c;可以对字符串进行分割、截取、查找…

网络变压器在网络分析仪上能通过测试,装上设备后网速达不到呢?

Hqst华轩盛(石门盈盛)电子导读&#xff1a;今天和大家一起探讨网络变压器在网络分析仪上能通过测试&#xff0c;装上设备后网通设备网速达不到的可能原因及其处理方式 一、出现这种情况可能有以下原因&#xff1a; 1.1. 设备兼容性问题&#xff1a;设备其它元器件与 网络…

快速掌握缓存技术:学习多个缓存供应商(ehcache,redis,memcached,jetcache,j2cache)

缓存技术 缓存模拟缓存Spring缓存技术第三方缓存技术Ehcache缓存供应Redis缓存memcached缓存&#xff08;国内&#xff09; jetcache缓存供应商jetcache的基本使用设置外部服务设置本地服务 jetcache方法缓存j2cache 缓存 什么是缓存 缓存是一种介于数据永久存储介质与数据应用…

graphviz嵌入latex的方法

效果&#xff1a; graphviz graphviz是一个开源的工具包&#xff0c;用DOT语言编写可以自动转换成图形&#xff0c;因为写法非常简单&#xff0c;只用代码描述好连接关系&#xff0c;就能直接得到最终的图形&#xff0c;所以优势很大。 latex&#xff1a; 就不介绍了 graphvi…

单片机项目中太多全局变量有什么弊端?

最近有读者遇到了这样的问题&#xff1a; 入职接到前同事丢下的“烂摊子”&#xff0c;项目中很多全局变量 问我&#xff1a;全局变量太多有哪些弊端&#xff1f;该如何规避&#xff0c;以及如何管理全局变量等。 全局变量太多有哪些弊端&#xff1f; 真正做过项目的同学应该都…

备战2024年上海初中生古诗文大会:单选题真题示例和独家解析

上海市中小学生的初中生古诗文大会——即上海中学生古诗文大会&#xff08;初中组&#xff09;和小学生古诗文大会&#xff08;比赛&#xff09;除了题型略有不同外&#xff0c;最主要的是考察的内容深度和广度不同&#xff0c;初中的题目中对于文言文的考察大幅增加&#xff0…

SpringBoot相关知识点总结

1 SpringBoot的目的 简化开发&#xff0c;开箱即用。 2 Spring Boot Starter Spring Boot Starter 是 Spring Boot 中的一个重要概念&#xff0c;它是一种提供依赖项的方式&#xff0c;可以帮助开发人员快速集成各种第三方库和框架。Spring Boot Starter 的目的是简化 Sprin…

C语言数据结构之顺序表

目录 1.线性表2.顺序表2.1顺序表相关概念及结构2.2增删查改等接口的实现 3.数组相关例题 1.线性表 线性表&#xff08;linear list&#xff09;是n个具有相同特性&#xff08;数据类型相同&#xff09;的数据元素的有限序列。 线性表是一种在实际中广泛使用的数据结构&#xff…

【RV1106的ISP使用记录之基础知识】硬件连接关系与设备树的构建

RV1106具备2个mipi csi2 dphy硬件&#xff0c;1个VICAP硬件和1个ISP硬件。其中&#xff1a; 1、mipi csi2 dphy 用于对数据流的解析&#xff0c;支持MIPC,LVDS,DVP三种接口&#xff1b; 2、VICAP用于数据流的捕获&#xff1b; 3、ISP用于对图像数据进行处理&#xff1b; 这三个…

【QTM中文教程】01:Quick Terrain Modeller介绍、下载与安装

文章目录 一、Quick Terrain Modeller简介二、Quick Terrain Modeller特点功能三、Quick Terrain Modeller下载安装1. 下载地址2. 安装教程一、Quick Terrain Modeller简介 Quick Terrain Modeler(QTM)是一种专业的地形建模软件,用于处理和分析地形数据。它提供了一系列功能…

【漏洞复现】泛微e-cology ProcessOverRequestByXml接口存在任意文件读取漏洞

漏洞描述 泛微e-cology依托全新的设计理念,全新的管理思想。 为中大型组织创建全新的高效协同办公环境。 智能语音办公,简化软件操作界面。 身份认证、电子签名、电子签章、数据存证让合同全程数字化。泛微e-cology ProcessOverRequestByXml接口存在任意文件读取漏洞 免责声…

【漏洞复现】宏景eHR showmediainfo SQL注入漏洞

0x01 产品简介 北京宏景世纪软件股份有限公司&#xff08;简称“宏景软件”&#xff09;专注于国有企事业单位人力与人才管理数智化&#xff08;数字化、智能化&#xff09;产品的研发和应用推广。 0x02 漏洞概述 宏景eHR /workbench/duty/showmediainfo接口存在SQL注入漏洞…