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

C++知识点更多,文章更用来查缺补漏更好,希望对你们有帮助


1.  类的6个默认成员函数

如果一个类中什么成员都没有,简称为空类。

但是类里面并不是什么都没有,实际上,在什么都不写的情况下,编译器会产生6个默认成员函数

默认成员函数:默认成员函数可以是自己创建的,也可以是编译器默认生成的成员函数

举例(以 构造函数 为例)

class A
{
   A()  //不带参数
   {
     ;
   }
   A(int x = 4)  //全缺省参数
   {
     ;
   }
};

 


注意事项:

类在实例化的时候一定会调用默认成员函数

2. 构造函数

a. 概念

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

b. 特性

1. 函数名与类名相同。

2. 无返回值。 (void 也不用写)

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

4. 构造函数可以重载。

代码举例

#include<iostream>
using namespace std;
class Data
{
public:
	Data(int year = 2, int month = 2, int day = 2)
	{
		_year = year;
		_month = month;
		_day = day;
		cout << _year << "年" << _month << "月" << _day << "日" << endl;
	}

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

int main()
{
	Data t;
	return 0;
}

运行结果:

分析:

我们可以看到在实例化的时候确实调用了默认成员函数

疑问:

为什么不可以写成 Data t() ?

因为这样编译器无法辨别它是构造函数还是一个函数的声明(返回类型:Data ,函数名:t , 参数:无参)

#include<iostream>
using namespace std;
class Data
{
public:
	Data(int year = 2, int month = 2, int day = 2)
	{
		_year = year;
		_month = month;
		_day = day;
		cout << _year << "年" << _month << "月" << _day << "日" << endl;
	}
	Data()
	{
		;
	}
	
private:
	int _year;
	int _month;
	int _day;
};

int main()
{
	Data t;
	return 0;
}

这两个成员函数不构成重载函数,编译器无法确定初始化的时候调用哪个函数

#include<iostream>
using namespace std;
class Data
{
public:
	Data(int year = 2, int month = 2, int day = 2)
	{
		_year = year;
		_month = month;
		_day = day;
		cout << _year << "年" << _month << "月" << _day << "日" << endl;
	}
	
private:
	int _year;
	int _month;
	int _day;
};

int main()
{
	Data t(2024,1,5);
	return 0;
}

运行结果:

 

c. 默认构造函数

默认构造函数:

  • 默认构造函数可以是自己创建的(不带参数,或者是 全缺省参数),也可以是编译器默认生成的成员函数
  • 默认构造函数只能有一个

如果类中没有构造函数,则C++编译器会自动生成一个无参的默认构造函数

对编译器生成的默认构造函数处理的猜测

编译器生成的默认构造函数对内置类型(如 int , char ,int * ... ...)不做处理(即得到的还是随机值), 对自定类型(如 calss, union ,struct ......)会自动调用它的构造函数

代码举例

#include<iostream>
using namespace std;
class Data
{
public:
	void Print()
	{
		cout << _year << "年" << _month << "月" << _day << "日" << endl;
	}
	
private:
	int _year;
	int _month;
	int _day;
};

int main()
{
	Data t;
	t.Print();
	return 0;
}

运行结果:

这里可以看出对 内置类型 没有做任何处理

#include<iostream>
using namespace std;
class Data
{
public:
	Data(int year = 0, int month = 0, int day = 0)
	{
		_year = year;
		_month = month;
		_day = day;
		cout << _year << "年" << _month << "月" << _day << "日" << endl;
	}
	
private:
	int _year;
	int _month;
	int _day;
};
class A
{
	Data t1;
	Data t2;
};
int main()
{
	A t3;
	return 0;
}

运行结果:

很明显:实例化对象 t1 , t2 的时候,同时也调用了 它的默认构造函数

注意事项:

C++11 中针对内置类型成员不初始化的缺陷,又打了补丁,即:内置类型成员变量在类中声明时可以给默认值

代码举例

#include<iostream>
using namespace std;
class Data
{
public:
	Data(int year = 0, int month = 0, int day = 0)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	
private:
	int _year;
	int _month;
	int _day;
};
class A
{
public:
	Data t1;
	Data t2;
	int size = 0;
};
int main()
{
	A t3;
	cout << t3.size << endl;
	return 0;
}

运行结果:

3.  析构函数

a. 概念

析构函数不是完成对对象本身的销毁,局部对象销毁工作是由编译器完成的。而对象在销毁时会自动调用析构函数,完成对象中资源的清理工作

b. 特性

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

2. 无参数无返回值类型。 (void 也不用写)

3. 一个类只能有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。注意:析构函数不能重载

4. 对象生命周期结束时,C++编译系统系统自动调用析构函数

代码举例

using namespace std;
class stack
{
public:
	stack(int n = 4)
	{
		int* pa = (int*)malloc(sizeof(int) * n);
		if (pa == NULL)
		{
			perror("malloc fail");
			return;
		}
		_a = pa;
		_top = 0;
		_capacity = 4;
		cout << "已开辟4字节空间大小" << endl;
	}
	~stack()
	{
		free(_a);
		_a = NULL;
		cout << "数组已经销毁了" << endl;
	}
private:
	int* _a;
	int _top;
	int _capacity;
};
int main()
{
	stack st;
	return 0;
}

运行结果:

 

 

c. 默认析构函数

  • 默认析构函数只能有一个

对编译器生成的默认析构函数处理的猜测

编译器生成的默认析构函数对内置类型(如 int , char ,int * ... ...)不做处理, 对自定义类型(如 class, union ,struct ......)会自动调用它的析构函数

代码举例

#include<iostream>
using namespace std;
class stack
{
public:
	stack(int n = 4)
	{
		int* pa = (int*)malloc(sizeof(int) * n);
		if (pa == NULL)
		{
			perror("malloc fail");
			return;
		}
		_a = pa;
		_top = 0;
		_capacity = 4;
		cout << "已开辟4字节空间大小" << endl;
	}
	~stack()
	{
		free(_a);
		_a = NULL;
		cout << "数组已经销毁了" << endl;
	}
private:
	int* _a;
	int _top;
	int _capacity;
};
class  ST
{
	stack t1;
	stack t2;
};
int main()
{
	ST t3;
	return 0;
}

运行结果:

 

 

注意事项:

  1. 如果类中没有申请资源时,析构函数可以不写,直接使用编译器生成的默认析构函数;有资源申请时,一定要写,否则会造成资源泄漏(比如 类中有动态开辟的的空间时,要写一个析构函数及时释放空间)
  2. 析构函数像栈一样,都是先进后出释放空间

举例

#include<iostream>
using namespace std;
class stack
{
public:
	~stack()
	{
		free(a);
		a = nullptr;
	}
private:
	int* a = (int*)malloc(sizeof(int) * 4);
};
int main()
{
	stack t1;
	stack t2;
	return 0;
}

销毁时,先调用的是后面的析构函数(先t2 , 再t1)

4.  拷贝构造函数

a. 概念

拷贝构造函数只有单个形参,该形参是对本类类型对象的引用(一般常用const修饰,是为了防止改变形参的值),在用已存在的类类型对象创建新对象时由编译器自动调用

举例

#include <iostream>
using namespace std;
class Data
{
public:
	Data(int year = 0,int month = 0,int day = 0)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	Data(Data& d)
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}
private:

	int _year;
	int _month;
	int _day;
};
int main()
{
	Data t1;
	Data t2(t1);
	return 0;
}

 

b. 特性

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

2. 拷贝构造函数的参数只有一个必须是类类型对象的引用,(使用传值方式编译器直接报错,因为会引发无穷递归调用)

注意事项:

传值传参时,如果参数是内置类型,如果直接拷贝给形参,但是如果是自定义类型,需要先调用形参的拷贝构造函数(编译器无法承担直接拷贝自定义类型变量的后果)

原因:

对于栈,如果直接拷贝,就会是这样:

如果调用默认析构函数,同一块空间就会被释放两次,所以直接拷贝自定义函数是行不通的

分析(对于参数不是类对象的引用 错误在哪)

class Data

{

pubilc:

Data(int year = 0 , int month = 0, int day = 0)

{

_year = year;

_month = month;

_day = day;

}

Data(Data d)

{

_year = d.year;

_month= d.month;

_day = d.day;

}

private:

int _year;

int _month;

int _day;

}

int mian()

{

Data d1;

Data d2(d1);

return 0;
}

c. 对编译器生成的默认拷贝构造函数处理的猜测

如果没有自己写的拷贝函数,调用拷贝构造函数调用的是 编译器生成的默认拷贝构造函数 (对传值会浅拷贝)

代码举例

#include <iostream>
using namespace std;
class Data
{
public:
	Data(int year = 0,int month = 0,int day = 0)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	void Print()
	{
		cout << _year << "/" << _month << "/" << _day << endl;
	}
private:

	int _year;
	int _month;
	int _day;
};

int main()
{
	Data t1(2023,1,8);
	Data t2(t1);
	t2.Print();
	return 0;
}

运行结果:

调用的是编译器自己生成的默认拷贝构造函数(即浅拷贝)

但某些情况下,我们需要自己去写拷贝构造函数,完成深拷贝(如:写栈的时候,不能浅拷贝,因为 定义的 int *a 如果是浅拷贝,则类定义的两个对象的成员变量a都是指向同一块空间)

5. 赋值运算符重载

a. 运算符重载

运算符重载是具有特殊函数名的函数 ,可以帮助实现自定义类型的大小比较

函数名字为:关键字operator后面接需要重载的运算符符号。(如:operator+)
注意事项:

  • 不能通过连接其他符号来创建新的操作符:比如operator@
  • 重载操作符必须有一个类类型参数
  • 用于内置类型的运算符,其含义不能改变(如:内置类型的 + 不能改变它原有的含义)
  • 作为类成员函数重载时,其形参看起来比操作数数目少1(因为成员函数的第一个参数为隐藏的this)
  • .* :: sizeof ?: . 注意以上5个运算符不能重载(这个经常在笔试选择题中出现 )

代码举例

 #include <iostream>
using namespace std;
class Data
{
public:
	Data(int year = 0,int month = 0,int day = 0)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	void Print()
	{
		cout << _year << "/" << _month << "/" << _day << endl;
	}
	bool operator ==(Data& d)
	{
		return _year == d._year && _month == d._month && _day == d._day;
	}
	bool operator >(Data& d)
	{
		return _year > d._year || ((_year == d._year) && (_month > d._month)) ||
			((_year == d._year) && (_month == d._month) && _day > d._day);
	}
	bool operator >=(Data& d)
	{
		return *this == d || *this > d;
	}
	bool operator <(Data& d)
	{
		return !(*this >= d);
	}
	bool operator <=(Data& d)
	{
		return *this == d || *this < d;
	}
private:

	int _year;
	int _month;
	int _day;
};

int main()
{
	Data t1(2023,1,8);
	Data t2(2023,1,8);
	if (t1 == t2)
	{
		printf("相等\n");
	}
	return 0;
}

运行结果:

b. 赋值运算符重载

将一个类定义的对象的值赋给另一个对象

代码举例

#include <iostream>
using namespace std;
class Data
{
public:
	Data(int year = 0,int month = 0,int day = 0)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	void Print()
	{
		cout << _year << "/" << _month << "/" << _day << endl;
	}
	Data& operator=(const Data& d)
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
		return *this;
	}
private:

	int _year;
	int _month;
	int _day;
};

int main()
{
	Data t1(2023,1,8);
	Data t2(2023,1,9);
  Data t3;
  t3 = t1 = t2;
	t1.Print();
  t3.Print();
	return 0;
}

运行结果:

分析:

为了能够连续赋值(参考内置类型的连续赋值:int i = j = k ,从右往左运行,把 k 赋值给了 j ,返回 j 的值,再把 j 赋值给 i ,返回 i 的值)

注意事项:

  • 参数类型:const T&(传递引用可以提高传参效率)
  • 返回值类型:T& (返回引用可以提高返回的效率,有返回值的是为了支持连续赋值)
  • 检测是否自己给自己赋值
  • 返回 *this :要复合连续赋值的含义
  • 赋值运算符重载成类的成员函数第一个参数是 this指针 ,重载成全局函数 没有 this 指针

对编译器生成的默认赋值运算符重载处理的猜测

和编译器默认生成的拷贝构造函数一样,都是浅拷贝(逐字节的拷贝)

代码举例

#include <iostream>
using namespace std;
struct Data
{
public:
	Data(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()
{
	Data t1(2024, 1, 12);
	Data t3;
	t3 = t1;
	t3.Print();
	return 0;
}

运行结果:

c. 前置++ 和 后置++ 重载

前置-- 和 后置--实现类似

代码举例

#include <iostream>
#include <assert.h>
using namespace std;
struct Data
{
public:
	Data(int year = 1, int month = 1, int day = 1)
  {
     if (year > 0 && month > 0 && month < 13 && day > 0 && day <= GetMonthDay(year, month))
     {
	     _year = year;
	     _month = month;
	     _day = day;
     }
      else
     {
	     cout << "日期错误" << endl;
	      exit(0);
     }
  }
	Data& operator+=(int day)
  {
     _day += day;
     while (_day > GetMonthDay(_year, _month))
     {
	     _day -= GetMonthDay(_year, _month);
	     _month++;
	       if (_month == 13)
	       {
		      _year++;
	      	_month = 1;
	       }
     }
      return *this;
  }
   
	int GetMonthDay(int year, int month)
  {
     int MonthDay[13] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };
     if (((year % 4 == 0) && (year % 100 != 0)) || year % 400 == 0)
      {
	      MonthDay[2]++;
      }
     return MonthDay[month];
  }
	Data& operator++()
  {
     *this += 1;
     return *this;
  }
	Data operator++(int)
  {
     Data tmp = *this;
     *this += 1;
     return tmp;
  }
private:
	int _year;
	int _month;
	int _day;
};
Data& Data ::operator++()
{
	*this += 1;
	return *this;
}
Data Data :: operator++(int) 
{
	Data tmp = *this;
	*this += 1;
	return tmp;
}

int main()
{
	Data t1(2024,2 ,23);
	t1++; //调用 t1.operator++()
	++t1;  //调用 t1.operator++(0)  参数也可能是其它 int 类型的数字
	return 0;
}

分析:

为了区分前置加加和后置加加,且能构成重载函数,参数一定要有一个 int 类型(无实际意义)

6. const 成员函数

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

代码举例

struct A
{
public:
	void fun() const
	{
		_a = 5;
	}
private:
	int _a = 4;
};
int main()
{
	A aa;
	aa.fun();
	return 0;
}

分析:

// 相当于 const A * this

所以 this 的成员的值不可以修改

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

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

代码举例

#include <iostream>
using namespace std;
struct A
{
private:
	int _a = 4;
};
int main()
{
	A aa;
	cout << &aa << endl;
	return 0;
}
include <iostream>
using namespace std;
struct A
{
	A* operator&()
	{
		return this;
	}
	const A* operator&() const
	{
		return this;
	}
private:
	int _a = 4;
};
int main()
{
	A aa;
	cout << &aa << endl;
	return 0;
}

相关推荐

  1. C++对象()

    2024-01-19 14:02:01       40 阅读

最近更新

  1. TCP协议是安全的吗?

    2024-01-19 14:02:01       16 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2024-01-19 14:02:01       16 阅读
  3. 【Python教程】压缩PDF文件大小

    2024-01-19 14:02:01       15 阅读
  4. 通过文章id递归查询所有评论(xml)

    2024-01-19 14:02:01       18 阅读

热门阅读

  1. linux c多个线程分段读取一个文件

    2024-01-19 14:02:01       31 阅读
  2. Vue 阻止事件冒泡

    2024-01-19 14:02:01       36 阅读
  3. Interface 接口

    2024-01-19 14:02:01       32 阅读
  4. Python设计模式详解

    2024-01-19 14:02:01       29 阅读