【C++篇】类和对象(中)

类的默认成员函数默认函数:我们不写,编译器默认生成的函数。无参构造函数全缺省构造函数初始与清理:构造函数与析构函数。构造函数(对象的初始化): 函数名与类名相同无返回值对象实例化时系统会自动调用对应的构造函数构造函数可以重载如果没有显示的写

【C++篇】类和对象(中)

类的默认成员函数

默认函数:

我们不写,编译器默认生成的函数。

无参构造函数

全缺省构造函数

初始与清理:构造函数与析构函数。

构造函数(对象的初始化):
  1. 函数名与类名相同
  2. 无返回值
  3. 对象实例化时系统会自动调用对应的构造函数
  4. 构造函数可以重载
  5. 如果没有显示的写构造函数,编译器会默认生成一个无参的构造
  6. 默认构造:无参构造,全缺省构造和编译器默认生成的构造
析构函数(清理数据):
  1. 析构函数在类名前加“~”
  2. 无返回值,无参数
  3. 生命周期结束时自动调用析构
  4. 一个类只有一个析构函数,若未显示析构,系统自动生成默认的析构函数
  5. 编译器自动生成的默认析构函数对内置类型不做处理,自定义类型成员会调用他的析构函数。
  6. 显示写的析构函数,自定义类型成员也会调用,就是说自定义类型成员无论什么情况都会调用析构函数
  7. 如果类中没有申请资源时,析构函数可以不写;但是有资源申请时,⼀定要 ⾃⼰写析构,否则会造成资源泄漏,如Stack。
  8. ⼀个局部域的多个对象,C++规定后定义的先析构。

一般情况下,显示申请了资源才需要自己写析构,其他情况基本都不需要写。

关于5,6点的解释:

代码语言:javascript代码运行次数:0运行复制
class Date
{
public:
	Date(int year=1,int date=1,int day=1)
	{
		_year = year;
		_date = date;
		_day = day;
	}
	~Date()
	{
		cout << "~Date()" << endl;
	}
private:
	int _year;
	int _date;
	int _day;
};

typedef int STDataType;
class Stack
{
public:
	Stack(int capacity = 4)
	{
		_Data = (STDataType*)malloc(sizeof(STDataType)*capacity);
		if (_Data==nullptr)
		{
			perror("申请空间失败");
			return;
		}
		_capacity = capacity;
		_top = 0;
	}
	~Stack()
	{
		cout << "~Stack()" << endl;
		if (_Data)
		{
			free(_Data);
		}
		_capacity = 0;
		_top = 0;
	}

private:
	STDataType* _Data;
	int _capacity;
	int _top;
};

class Myqueue
{
public:
	/*~Myqueue()
	{
		cout << "~Myqueue()" << endl;
	}*/
private:
	Stack _push;
	Stack _pop;
};
int main()
{
	Date d1;
	Stack s1;
	Myqueue q1;
	return 0;
}
//Myqueue类的析构函数被注释掉,没有显示的析构函数,所以编译器自动生成一个默认的析构函数,因为Myqueue成员变量是自定义类型Stack,所以会调用~Stack(),因为有两个Stack类型的成员变量,所以调用两次;
//取消Myqueue的注释,现在有显示析构函数,那么显示析构函数会被调用,自定义类型变量的析构函数也会被调用
//在main函数中,有三个对象,那么后定义的先析构,先析构Myqueue q1,在析构Stack s1,最后Date d1
拷贝构造函数:
  1. 是构造函数的重载
  2. 拷贝构造函数的第一个参数必须是类类型对象的引用(Date& d ),传值⽅式编译器直接报错,因为语法逻 辑上会引发⽆穷递归调⽤,拷⻉构造函数也可以多个参数,但是第⼀个参数必须是类类型对象的引 ⽤,后⾯的参数必须有缺省值。
  3. C++规定⾃定义类型对象进⾏拷⻉⾏为必须调⽤拷⻉构造,所以这⾥⾃定义类型传值传参和传值返 回都会调⽤拷⻉构造完成。
  4. 若未显式定义拷⻉构造,编译器会⽣成⾃动⽣成拷⻉构造函数。
  5. 拷贝构造中引用换成指针也可以,功能上看起来像拷贝构造,但是这样就不是拷贝构造了,就是一个普通构造函数。
  6. 自动生成的拷贝构造对内置类型成员变量会完成值拷贝/浅拷贝(一个一个字节的拷贝),对自定义类型成员变量会调用他的拷贝构造。浅拷贝是系统的吗,默认行为。对于栈类类型的对象,不能进行浅拷贝,这样会造成对同一块空间析构两次的问题,所以对于栈类类型的对象,要进行深拷贝(开一样大的空间,里面的值是一样的)。
代码语言:javascript代码运行次数:0运行复制
typedef int STDataType;
class Stack
{
public:
	Stack(int capacity = 4)//构造
	{
		_Data = (STDataType*)malloc(sizeof(STDataType) * capacity);
		if (_Data == nullptr)
		{
			perror("申请空间失败");
			return;
		}
		_capacity = capacity;
		_top = 0;
	}

	Stack(Stack& st)//拷贝构造,深拷贝构造,因为这里存在资源的申请
	{//如果没有这个深拷贝,用系统自动生成的默认拷贝构造,会报错,因为代码中存在资源的申请
      //在代码结束时,Stack对象会调用析构函数,但是st1与st2的成员变量_a指向的是同一块空间,所以在析构时会造成对同一块空间进行两次释放的问题。
		_Data = (STDataType*)malloc(sizeof(STDataType)*st._capacity);
		if (_Data==nullptr)
		{
			perror("申请空间失败");
			return;
		}
		memcpy(_Data,st._Data,sizeof(STDataType)*st._top);
		_capacity = st._capacity;
	}

	~Stack()
	{
		cout << "~Stack()" << endl;
		if (_Data)
		{
			free(_Data);
		}
		_capacity = 0;
		_top = 0;
	}

private:
	STDataType* _Data;
	int _capacity;
	int _top;
};
int main()
{
	Stack st1;
	Stack st2(st1);
	return 0;
}
  1. 类成员变量全是内置类型且没有指向什么资源,编译器⾃动⽣成的拷⻉构造就可以完 成需要的拷⻉,所以不需要我们显⽰实现拷⻉构造。像Stack这样的类,虽然也都是内置类型,但 是_a指向了资源,编译器⾃动⽣成的拷⻉构造完成的值拷⻉/浅拷⻉不符合我们的需求,所以需要 我们⾃⼰实现深拷⻉(对指向的资源也进⾏拷⻉)。这⾥还有⼀个⼩技巧,如果⼀个类显⽰实现了析构并释放资源,那么他就 需要显⽰写拷⻉构造,否则就不需要。
  2. 传值返回会产⽣⼀个临时对象调⽤拷⻉构造,传引⽤返回,返回的是返回对象的别名(引⽤),没 有产⽣拷⻉。但是如果返回对象是⼀个当前函数局部域的局部对象,函数结束就销毁了,那么使⽤ 引⽤返回是有问题的,这时的引⽤相当于⼀个野引⽤,类似⼀个野指针⼀样。传引⽤返回可以减少 拷⻉,但是⼀定要确保返回对象,在当前函数结束后还在,才能⽤引⽤返回。
代码语言:javascript代码运行次数:0运行复制
typedef int STDataType;
class Stack
{
public:
	Stack(int capacity = 4)//构造
	{
		_Data = (STDataType*)malloc(sizeof(STDataType) * capacity);
		if (_Data == nullptr)
		{
			perror("申请空间失败");
			return;
		}
		_capacity = capacity;
		_top = 0;
	}
	

	Stack(Stack& st)//拷贝构造
	{
		_Data = (STDataType*)malloc(sizeof(STDataType)*st._capacity);
		if (_Data==nullptr)
		{
			perror("申请空间失败");
			return;
		}
		memcpy(_Data,st._Data,sizeof(STDataType)*st._top);
		_capacity = st._capacity;
	}

	~Stack()
	{
		cout << "~Stack()" << endl;
		if (_Data)
		{
			free(_Data);
		}
		_capacity = 0;
		_top = 0;
	}
    void push(int x)
	{
		_Data[_top++] = x;
	}
private:
	STDataType* _Data;
	int _capacity;
	int _top;
};

Stack& func()
{
  Stack st;
  st.push(1);
  st.push(2);
  st.push(3);
  //.....
  return st;//这里返回了局部域的对象,函数结束时,st就销毁了,
}
//返回st,把st的值拷贝到一块临时空间中去,此时这块临时空间就是临时对象,这一过程要调用拷贝构造,再把临时对象的值拷贝给ret,这一过程也要调用拷贝构造,但是当func函数结束时,st对象中指向的资源就已经被释放,所以ret对象存在野引用,当main函数结束时调用析构函数,对ret对象进行析构就存在对已经释放的空间进行析构的问题。 
int main()
{
  Stack ret=Func();
  return 0;
}
赋值运算符重载:
运算符重载:(运算符重载是由operator加要重载的符号构成 )
  1. 当运算符被用于类类型的对象时,c++语言允许我们通过运算符重载的形式指定新的含义。c++规定类类型对象使用运算符时,要转换成他对应的运算符重载,若没有对应的运算符重载,编译器将会报错
  2. 运算符重载是具有特殊名字的函数,他的名字是由operator和后⾯要定义的运算符共同构成。和其 他函数⼀样,它也具有其返回类型和参数列表以及函数体。
  3. 重载运算符函数的参数个数和该运算符作⽤的运算对象数量⼀样多。⼀元运算符有⼀个参数,⼆元 运算符有两个参数,⼆元运算符的左侧运算对象传给第⼀个参数,右侧运算对象传给第⼆个参数。
  4. 如果⼀个重载运算符函数是成员函数,则它的第⼀个运算对象默认传给隐式的this指针,因此运算 符重载作为成员函数时,参数⽐运算对象少⼀个。
  5. 运算符重载以后,其优先级和结合性与对应的内置类型运算符保持⼀致。
  6. 不能通过连接语法中没有的符号来创建新的操作符:⽐如operator@。
  7. .* 、:: 、sizeof 、?:、 . 注意以上5个运算符不能重载。
代码语言:javascript代码运行次数:0运行复制
class A
{
public:
	 void func()
	 {
		cout << "A::func()" << endl;
	 }
};

typedef void(A::*Pf)();//定义一个函数指针,指向的函数返回类型为void,是在A类中的并且参数个数为0,所以函数指针类型为void(A::*)();

int main()
{
	Pf pf;
	pf = &A::func;
	A obj; 
	(obj.*pf)();

	return 0;
}

重载操作符至少有一个类类型参数,不能通过运算符重载改变内置类型对象的含义,如:int operator+(int x,int y)

关于调用运算符重载函数的形式:

代码语言:javascript代码运行次数:0运行复制
Date operator+(int day);

Date Date::operator+(int day)
{
	Date tmp = *this;
	tmp._day += day;
	while (tmp._day > GetMonthDay(tmp._year,tmp._month))
	{
		tmp._day -= GetMonthDay(tmp._year, tmp._month);
		++tmp._month;
		if (tmp._month == 13)
		{
			++tmp._year;
			tmp._month = 1;
		}
	}
	return tmp;
}
int main()
{
  Date d1;
  d1+1;//可以这么写
  d1.operator(1);//这样写也对
  //写成第一种形式编译器会自动转换成第二种形式。
  //从底层的角度来讲,编译器调用函数会转换成一串指令call,这两种形式的底层汇编代码是一样的
  return 0;
}
日期类Date:
取地址运算符重载:
  1. 将const修饰的成员函数称之为const成员函数,const修饰成员函数放到成员函数参数列表的后面。
  2. const实际修饰成员函数隐含的this指针,表面在该成员函数中不能对类的任何成员进行修改。
代码语言:javascript代码运行次数:0运行复制
#include<iostream>
using namespace std;

class Date
{
public:
	Date(int year=2000,int month=1,int day=1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	void Print()const//const修饰Date类的Print成员函数,Print的this指针变成:
      //(const Date*const this)
	{
		//(*this)._day = 30;
		cout << _year << "-" << _month << "-" << _day << endl;
	}

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


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

	const Date d2(2024,10,10);
	d2.Print();


	return 0;
}
取地址运算符重载:

取地址运算符重载分为普通取地址运算符重载和const取地址运算符重载,⼀般这两个函数编译器⾃动 ⽣成的就可以够我们⽤了,不需要去显⽰实现。

除⾮⼀些很特殊的场景,⽐如我们不想让别⼈取到当 比特就业课 前类对象的地址,就可以⾃⼰实现⼀份,胡乱返回⼀个地址。

代码语言:javascript代码运行次数:0运行复制
class Date
{
public :
  Date* operator&()
  {
    return this;
    // return nullptr;
  }
  const Date* operator&()const
  {
    return this;
    // return nullptr;
  }
private :
  int _year ; // 年
  int _month ; // ⽉
  int _day ; // ⽇
 };
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。 原始发表:2025-05-04,如有侵权请联系 cloudcommunity@tencent 删除变量编译器对象函数c++

发布者:admin,转转请注明出处:http://www.yc00.com/web/1747397986a4640416.html

相关推荐

  • 【C++篇】类和对象(中)

    类的默认成员函数默认函数:我们不写,编译器默认生成的函数。无参构造函数全缺省构造函数初始与清理:构造函数与析构函数。构造函数(对象的初始化): 函数名与类名相同无返回值对象实例化时系统会自动调用对应的构造函数构造函数可以重载如果没有显示的写

    1天前
    10

发表回复

评论列表(0条)

  • 暂无评论

联系我们

400-800-8888

在线咨询: QQ交谈

邮件:admin@example.com

工作时间:周一至周五,9:30-18:30,节假日休息

关注微信