当前位置: 首页 > article >正文

c++ 类和对象 —— 中 【复习笔记】

1. 类的默认成员函数

如果一个类什么成员都没有,简称空类。但实际上,任何类在不写成员时,编译器会自动生成6个默认成员函数(用户未显式实现,编译器生成的成员函数)

这6个成员函数可分为三类:

1. 初始化和清理:构造函数完成初始化工作;析构函数完成清理工作

2. 拷贝复制:拷贝构造是用同类对象初始化创建对象;赋值重载是把一个对象赋值给另一个对象

3. 取地址重载:普通对象和const对象取地址,这两个很少自己实现

2. 构造函数

2.1 引入

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

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

对于上面的Date类,如果每次创建对象后要设置信息都要调用 Init 的公有方法,会有些麻烦。那能不能在创建对象时就能设置信息呢?构造函数便出现了

构造函数是一个特殊的成员函数,创建类类型对象时由编译器自动调用,在对象整个生命周期内只调用一次

2.2 简介

构造函数虽然叫构造,但它的作用不是开空间创建对象而是初始化对象

它具有以下特性:

1. 函数名和类名相同

2. 无返回值

3. 构造函数可以重载

4. 对象实例化时编译器自动调用

5. 如果类没有显式定义构造函数,那编译器会自动生成一个无参的默认构造函数;如果类显式定义了,那编译器不再生成

6. 构造函数对于类中内置类型成员不初始化,对于自定义类型成员调用它的默认成员函数(c++11中对于这一点打了补丁:内置类型成员在类中声明时可以给默认值

7. 无参的构造函数和全缺省的构造函数都称为默认构造函数,并且默认构造函数只有一个(即:无参构造函数、全缺省构造函数、编译器自己默认生成的构造函数都是默认构造函数,但为了避免二义性,通常只定义一种)

​
class Time
{
public:
	Time()
	{
		cout << "Time()" << endl;
		_hour = 0;
		_minute = 0;
		_second = 0;
	}

private:
	int _hour;
	int _minute;
	int _second;
};

class Date
{
public:
	/*
    //一旦显式定义任何构造函数,编译器将不再生成
	Date(int year, int month, int day)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	*/
	


	//无参构造函数
	//如果使用无参构造函数创建对象,对象后不加括号,否则就变成函数声明
	Date()
	{}
	//带参构造函数
	Date(int year, int month, int day)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	//全缺省构造函数
	//全缺省和无参可以看为函数重载,但在调用时会出现二义性
	Date(int year = 2025, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}

private:
	//内置类型,c++11内置成员变量可以在声明时给默认值
	int _year;
	int _month;
	int _day;

	//自定义类型
	Time t;
};

int main()
{
	Date d1;//调用无参构造函数
	Date d2(2025, 1, 1);//带参构造函数
	return 0;
}

​

3. 析构函数

3.1 引入

构造函数是完成初始化对象的工作的,和构造函数相反,析构函数就是完成对象中资源的清理工作(析构函数不完成对对象本身销毁),对象销毁时编译器自动调用

3.2 简介

析构函数具有以下特征:

1. 析构函数名是类名前加字符 ~

2. 无参数无返回类型

3. 一个类只能有一个析构函数,不能重载。如果没显式定义,系统自动生成默认析构函数

4. 对象生命周期结束,编译器自动调用析构函数

5. 对于对象中内置类型,编译器不需要资源清理,最后系统直接将其内存回收即可;对于自定义类型,会调用它的析构函数

6. 如果类没有申请资源,析构函数可以不写,使用生成的默认析构函数即可;但有资源申请时,一定要写,否则会资源泄露

class Time
{
public:
	~Time()
	{
		cout << "~Time()" << endl;
	}
private:
	int _hour;
	int _minute;
	int _second;
};

class Date
{
private:
	//内置类型
	int _year = 2025;
	int _month = 1;
	int _day = 1;

	//自定义类型
	Time t;
	//创建Date对象,销毁时要将Time类对象t销毁,编译器会给Date类生成一个默认析构函数
	//目的是调用Time类析构函数,确保Date对象销毁时内部每个自定义对象都可以销毁
};

4. 拷贝构造函数

4.1 引入

如果我们要创建一个和已经存在对象一样的新对象,那拷贝构造函数便发挥作用

拷贝构造函数目的是用已经存在的类类型对象创建新对象,编译器自动调用。所以它只有一个形参(对该类类型对象的引用,一般用const修饰)

4.2 简介

它的特征如下:

1. 拷贝构造函数是构造函数的一个重载形式

2. 参数只有一个必须是该类类型对象的引用(用传值方式直接报错 --- 会引发无穷递归)

3. 如果没有显式定义,编译器自动生成默认拷贝构造函数。(默认拷贝构造函数按内存存储字节序完成拷贝,这种拷贝叫浅拷贝或值拷贝)

4. 对于默认拷贝构造函数,内置类型是浅拷贝,而自定义类型是调用其拷贝构造函数完成拷贝

(调用拷贝构造要传参,传参就要拷贝构造,拷贝构造就要传参,传参就要........,这也是为什么如果参数不是传引用,就会引发无穷递归)

5. 如果类中没涉及资源申请,拷贝构造可写可不写;一旦涉及资源申请,拷贝构造一定要写,否则是浅拷贝。

(比如:我们创建了一个栈对象s1,在它的构造函数中申请10个元素的空间;然后对象s2使用s1拷贝构造,而类中没有显式定义拷贝构造函数,那编译器自动生成默认拷贝构造函数,而这个拷贝构造是值拷贝,就会导致s1和s2指向同一块空间。当销毁s1和s2时,这个空间会释放多次,导致程序崩溃)

6. 拷贝构造的调用场景:

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

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

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

一般对象传参使用引用类型;返回时,如果场景允许引用(如非局部类对象.....)尽量使用引用

​
#include<iostream>
using namespace std;
class Date
{
public:
	Date(int year, int month, 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)
{
	//调用拷贝构造函数创建temp
	Date temp(d);
	//以值方式返回,调用拷贝构造构造临时变量
	return temp;
}

int main()
{
    //构造函数创建d1
	Date d1(2025, 1, 1);
	//传值方式传递,调用拷贝构造函数
	Test(d1);
	return 0;
}

​

5. 运算符重载

5.1 运算符重载

运算符重载是具有特殊函数名的函数,有函数名字、参数列表和返回值类型,返回值类型和参数列表和普通函数相似

函数名字:operator+要重载运算符符合(比如:operator< )

函数原型:返回值类型 operator操作符(参数列表)

注意事项:

1. 不能连接其他符号创建新操作符:operator@(这是错误的)

2. 重载操作符必须有一个类类型参数(即必须有一个自定义类型),操作符有几个操作数,重载函数就有几个参数

3. 用于内置类型的运算符,含义不能变

4. 作为类成员函数重载,形参看起来比操作数少1(成员函数第一个参数为隐藏的this)

5. 以下5个运算符不能重载:.*    ::    sizeof     ? :     .

class Date
{
public:
	Date(int year, int month, int day)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	
	//左操作数为this指针
bool operator==(const Date& d2)
{
	return _year == d2._year
		&& _month == d2._month
		&& _day == d2._day;
}

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

//全局operator==
//如果成员变量是私有的,那无法访问;重载成成员函数即可
/*bool operator==(const Date& d1, const Date& d2)
{
	return d1._year == d2._year
		&& d1._month == d2._month
		&& d1._day == d2._day;
}*/

void Test()
{
	Date d1(2025, 1, 1);
	Date d2(2025, 2, 2);
}

5.2 赋值运算符重载

1. 格式:

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

返回值类型:T& ,返回引用提高效率,便于支持连续赋值

检测是否给自己赋值

返回*this:符合连续赋值含义

2.特征:

 1. 赋值运算符只能重载成类的成员函数,不能重载成全局函数

(如果赋值运算符不显示实现,编译器默认生成。那再在类外自己实现一个全局赋值运算符重载,会导致冲突)

 2. 用户没显式实现,编译器自动生成一个默认赋值运算符重载(值拷贝的方式)

(内置类型成员变量直接复制,自定义类型要调用对应类的复制运算符重载)

3. 如果类为涉及资源管理,赋值运算符可写可不写;但涉及资源管理必须自己实现

(比如:我们创建了一个栈对象s1,在它的构造函数中申请10个元素的空间;然后对象s2的构造函数也申请10个元素的空间,s2=s1,编译器会将s1的内容原封不动拷贝给s2,这导致s2的原空间丢失,内存泄漏;s1和s2共享一块空间,后续会导致一份空间释放两次)

class Time
{
public:
    Time()
    {
        _hour = 1;
        _minute = 1;
        _second = 1;
    }
    Time& operator=(const Time& t)
    {
        if (this != &t)
        {
            _hour = t._hour;
            _minute = t._minute;
            _second = t._second;
        }
        return *this;
    }
private:
    int _hour;
    int _minute;
    int _second;
};

class Date
{
public:
    Date(int year = 2025, 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;

    //自定义类型
    Time t;
};

5.3 前置++和后置++重载

class Date
{
public:
	Date(int year = 2025, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	//前置++
	//this指向对象在函数结束后不销毁,用传引用
	Date& operator++()
	{
		_day += 1;
		return *this;
	}

	//后置++
	//为了让前置++和后置++区别,c++规定:后置++重载时多加一个int类型参数
	//但调用函数时这个参数不用传递,编译器自动传递
	//tmp为临时对象,只能传值返回
	Date operator++(int)
	{
		Date tmp(*this);
		_day += 1;
		return tmp;
	}

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

6. const成员

const修饰的“成员函数”称为const成员函数,实际修饰该成员函数隐藏this指针,表示该成员函数中不能对类的任何成员进行修饰

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

		//实际看为void print(const Date*this)
		void print()const
		{
			cout << "Print()const" << endl;
			cout << _year << endl;
			cout << _month << endl;
			cout << _day << endl;
		}
	
	private:
		int _year;
		int _month;
		int _day;
	};

注意事项:

1. 成员函数后面加const后,普通和const对象都可以调用

2. 不能所有成员函数都加const,比如要修改的对象成员变量的函数不能加

3. 只有成员函数内部不修改成员变量,都应该加const,这样const对象和普通对象都可以调用

7. 取地址和const取地址操作符重载

这两个默认成员函数一般不重新定义,编译器会自己生成

class Date
	{
	public:
		Date* operator&()
		{
			return this;
		}

		const Date* operator&()const
		{
			return this;
		}

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

使用编译器默认取地址重载即可,特殊情况如:想让别人获取到指定内容,才重载


http://www.kler.cn/a/589783.html

相关文章:

  • 物联网中RFID标签需要人为赋予信息和手动粘贴/挂载的问题
  • 【NeurIPS 2024】LLM-ESR:用大语言模型破解序列推荐的长尾难题
  • 4张图,9个方法,搞定 “信贷风控策略调优”
  • 使用unplugin-auto-import自动导入vue3的api,不需要在每一个.vue文件中重复去导入操作
  • 蓝桥杯嵌入式赛道复习笔记1(按键控制LED灯,双击按键,单击按键,长按按键)
  • SpringBoot(6)——Springboot整合springmvc
  • 量子计算 × 虚拟现实:未来科技的双剑合璧
  • 遥感数据处理
  • Linux:信号的生命周期分析,以及捕捉信号时中断触发的内核态拦截与用户态处理时机
  • 下拉菜单+DoTween插件
  • Houdini :《哪吒2》神话与科技碰撞的创新之旅
  • C语言经典代码题
  • 从 YOLOv1 到 YOLOv2:目标检测的进化之路
  • 轨迹规划:基于查找的(search-based)路径规划算法
  • C#特性和反射
  • MySQL高频八股——事务过程中Undo log、Redo log、Binlog的写入顺序(涉及两阶段提交)
  • 异常(11)
  • Linux 日志与时间同步指南
  • 2024浙江大学计算机考研上机真题
  • 【蓝桥杯】省赛:神奇闹钟