自从学了C++之后,小雅兰就有对象了!!!(类与对象)(中)——“C++”

这篇具有很好参考价值的文章主要介绍了自从学了C++之后,小雅兰就有对象了!!!(类与对象)(中)——“C++”。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

各位CSDN的uu们好呀,好久没有更新小雅兰的C++专栏啦,话不多说,让我们进入类和对象的世界吧!!!


类的6个默认成员函数

构造函数

析构函数

拷贝构造函数


类的6个默认成员函数

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

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

默认成员函数:用户没有显式实现,编译器会生成的成员函数称为默认成员函数。

class Date {};

自从学了C++之后,小雅兰就有对象了!!!(类与对象)(中)——“C++”,C++,c++,开发语言,c语言,数据结构,算法


构造函数

概念

对于以下Date类:

#include<iostream>
using namespace std;
class Date
{
public:
	void Init(int year, int month, int day)
	{
		_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(2023, 8, 9);
	d1.Print();
	Date d2;
	d2.Init(2023, 8, 10);
	d2.Print();
	return 0;
}

对于Date类,可以通过 Init 公有方法给对象设置日期,但如果每次创建对象时都调用该方法设置信息,未免有点麻烦,那能否在对象创建时,就将信息设置进去呢?

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

特性

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

也就是说,构造函数类比于Init函数!!!

其特征如下:

  • 函数名与类名相同。
  • 无返回值(不需要写void)。
  • 对象实例化时编译器自动调用对应的构造函数。
  • 构造函数可以重载(本质就是写多个构造函数,提供多种初始化方式)。
class Date
{
public:
	//无参构造函数
	Date()
	{
		cout << "Date()" << endl;
		_year = 1;
		_month = 1;
		_day = 1;
	}
	//带参构造函数
	Date(int year, int month, int day)
	{
		cout << "Date(int year, int month, int day)" << endl;
		_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.Print();
	// 调用带参的构造函数
	Date d2(2023, 8, 9);
	d2.Print();
	// 注意:如果通过无参构造函数创建对象时,对象后面不用跟括号,否则就成了函数声明
	// 以下代码的函数:声明了d3函数,该函数无参,返回一个日期类型的对象
	// warning C4930: “Date d3(void)”: 未调用原型函数(是否是有意用变量定义的?)
	Date d3();
	d3.Print();

	return 0;
}

自从学了C++之后,小雅兰就有对象了!!!(类与对象)(中)——“C++”,C++,c++,开发语言,c语言,数据结构,算法

自从学了C++之后,小雅兰就有对象了!!!(类与对象)(中)——“C++”,C++,c++,开发语言,c语言,数据结构,算法

//无参构造函数
    Date()
    {
        cout << "Date()" << endl;
        _year = 1;
        _month = 1;
        _day = 1;
    }
    //带参构造函数
    Date(int year, int month, int day)
    {
        cout << "Date(int year, int month, int day)" << endl;
        _year = year;
        _month = month;
        _day = day;
    } 

上述两个函数其实可以合并一下,写成全缺省参数的形式。

//带参构造函数
Date(int year = 1, int month = 1, int day = 1)
{
	cout << "Date(int year, int month, int day)" << endl;
	_year = year;
	_month = month;
	_day = day;
}

自从学了C++之后,小雅兰就有对象了!!!(类与对象)(中)——“C++”,C++,c++,开发语言,c语言,数据结构,算法

这样的写法也更灵活了,可以传一个参数,也可以传两个参数,也可以传三个参数,也可以不传参数。

自从学了C++之后,小雅兰就有对象了!!!(类与对象)(中)——“C++”,C++,c++,开发语言,c语言,数据结构,算法

 再看下面这个实例:

class Stack
{
public:
	Stack(size_t n = 4)
	{
		if (n == 0)
		{
			a = nullptr;
			top = capacity = 0;
		}
		else
		{
			a = (int*)malloc(sizeof(int) * n);
			if(a == nullptr)
			{
				perror("realloc fail");
				exit(-1);
			}

			top = 0;
			capacity = n;
		}
	}
	void Push(int x)
	{
		if (top == capacity)
		{
			size_t newcapacity = capacity == 0 ? 4 : capacity * 2;
			int* tmp = (int*)realloc(a, sizeof(int) * newcapacity);
			if (tmp == nullptr)
			{
				perror("realloc fail");
				exit(-1);
			}
			if (tmp == a)
			{
				cout << capacity << "原地扩容" << endl;
			}
			else
			{
				cout << capacity << "异地扩容" << endl;
			}

			a = tmp;
			capacity = newcapacity;
		}

		a[top++] = x;
	}

	int Top()
	{
		return a[top - 1];
	}

	void Pop()
	{
		assert(top > 0);
		--top;
	}

	void Destroy()
	{
		free(a);
		a = nullptr;
		top = capacity = 0;
	}

	bool Empty()
	{
		return top == 0;
	}
private:
	// 成员变量
	int* a;
	int top;
	int capacity;
};

int main()
{
	Stack st1;
	st1.Push(1);
	st1.Push(2);
	st1.Push(3);
	st1.Push(4);
	
	while (!st1.Empty())
	{
		cout << st1.Top() << " ";
		st1.Pop();
	}
	cout << endl;

	st1.Destroy();

	//Stack st2(1000);
	Stack st2;
	for (size_t i = 0; i < 1000; i++)
	{
		st2.Push(i);
	}

	while (!st2.Empty())
	{
		cout << st2.Top() << " ";
		st2.Pop();
	}
	cout << endl;

	st2.Destroy();
	return 0;
}

自从学了C++之后,小雅兰就有对象了!!!(类与对象)(中)——“C++”,C++,c++,开发语言,c语言,数据结构,算法

自从学了C++之后,小雅兰就有对象了!!!(类与对象)(中)——“C++”,C++,c++,开发语言,c语言,数据结构,算法

构造函数,是默认成员函数,不写,编译器会自动生成。

编译生成的默认构造的特点:

  • 我们不写才会生成,我们写了就不会生成了。
  • 内置类型的成员不会处理(C++11,声明支持给缺省值)。
  • 自定义类型的成员才会处理,会去调用这个成员的默认构造函数。

如果类中没有显式定义构造函数,则C++编译器会自动生成一个无参的默认构造函数,一旦 用户显式定义编译器将不再生成。

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;
	int _month;
	int _day;
};

int main()
{
	// 将Date类中构造函数屏蔽后,代码可以通过编译,因为编译器生成了一个无参的默认构造函数
	// 将Date类中构造函数放开,代码编译失败,因为一旦显式定义任何构造函数,编译器将不再生成
	// 无参构造函数,放开后报错:error C2512: “Date”: 没有合适的默认构造函数可用
	Date d1;
	d1.Print();
	return 0;
}

自从学了C++之后,小雅兰就有对象了!!!(类与对象)(中)——“C++”,C++,c++,开发语言,c语言,数据结构,算法

自从学了C++之后,小雅兰就有对象了!!!(类与对象)(中)——“C++”,C++,c++,开发语言,c语言,数据结构,算法

所以,默认生成的构造函数一般没什么价值,但是在有一些场景下非常有价值,之前小雅兰写过一个题目,就是两个栈实现一个队列,既可以写构造函数,也可以不写。

// 两个栈实现一个队列
class MyQueue
{
private:
	Stack _pushst;
	Stack _popst;
};

总结:一般情况都需要我们自己写构造函数,决定初始化方式

成员变量全是自定义类型,可以考虑不写构造函数

关于编译器生成的默认成员函数,很多人会有疑惑:不实现构造函数的情况下,编译器会 生成默认的构造函数。但是看起来默认构造函数又没什么用?d对象调用了编译器生成的默 认构造函数,但是d对象_year/_month/_day,依旧是随机值。也就说在这里编译器生成的默认构造函数并没有什么用??

解答:C++把类型分成内置类型(基本类型)和自定义类型。内置类型就是语言提供的数据类 型,如:int/char...,自定义类型就是我们使用class/struct/union等自己定义的类型,看看 下面的程序,就会发现编译器生成默认的构造函数会对自定类型成员_t调用的它的默认构造函数。

int*和Date*(指针)都是内置类型!!!

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

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

class Date
{
private:
	// 基本类型(内置类型)
	int _year;
	int _month;
	int _day;
	// 自定义类型
	Time _t;
};

int main()
{
	Date d;
	return 0;
}

自从学了C++之后,小雅兰就有对象了!!!(类与对象)(中)——“C++”,C++,c++,开发语言,c语言,数据结构,算法

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

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

class Date
{
private:
	// 基本类型(内置类型)
	int _year = 1970;
	int _month = 1;
	int _day = 1;
	// 自定义类型
	Time _t;
};

int main()
{
	Date d;
	return 0;
}

无参的构造函数和全缺省的构造函数都称为默认构造函数,并且默认构造函数只能有一个。 注意:无参构造函数、全缺省构造函数、我们没写编译器默认生成的构造函数,都可以认为 是默认构造函数。 

不传参就可以调用的构造就是默认构造!!!

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

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

// 以下测试函数能通过编译吗?
void Test()
{
	Date d1;
}
int main()
{
	Test();
	return 0;
}

上述代码是无法通过编译的。

如果是把无参构造函数屏蔽掉或者是把全缺省构造函数屏蔽掉,就可以通过编译了!!!!


析构函数

概念

通过前面构造函数的学习,我们知道一个对象是怎么来的,那一个对象又是怎么没呢的?

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

也就是说,析构函数类比于Destroy函数!!!

特性

析构函数是特殊的成员函数,其特征如下:

  • 析构函数名是在类名前加上字符 ~。
  • 无参数无返回值类型。
  • 一个类只能有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。注意:析构 函数不能重载。
  • 对象生命周期结束时,C++编译系统系统自动调用析构函数。

默认的析构函数跟默认构造函数类似:内置类型成员不会处理,自定义类型成员会调用这个成员的析构函数。

class Date
{
public:
	Date(int year = 1, int month = 1, int day = 1)
	{
		cout << "Date(int year = 1, int month = 1, int day = 1)" << endl;

		_year = year;
		_month = month;
		_day = day;
	}
	void Print()
	{
		cout << _year << "/" << _month << "/" << _day << endl;
	}

	~Date()
	{
		cout << "~Date()" << endl;
	}

private:
	int _year = 1;   // 声明给的缺省值
	int _month = 1;
	int _day = 1;
};

class Stack
{
public:
	Stack(size_t n = 4)
	{
		cout << "Stack(size_t n = 4)" << endl;

		if (n == 0)
		{
			a = nullptr;
			top = capacity = 0;
		}
		else
		{
			a = (int*)malloc(sizeof(int) * n);
			if (a == nullptr)
			{
				perror("realloc fail");
				exit(-1);
			}

			top = 0;
			capacity = n;
		}
	}

	~Stack()
	{
		cout << "~Stack()" << endl;
		free(a);
		a = nullptr;
		top = capacity = 0;
	}

	void Push(int x)
	{
		if (top == capacity)
		{
			size_t newcapacity = capacity == 0 ? 4 : capacity * 2;
			int* tmp = (int*)realloc(a, sizeof(int) * newcapacity);
			if (tmp == nullptr)
			{
				perror("realloc fail");
				exit(-1);
			}
			if (tmp == a)
			{
				cout << capacity << "原地扩容" << endl;
			}
			else
			{
				cout << capacity << "异地扩容" << endl;
			}

			a = tmp;
			capacity = newcapacity;
		}

		a[top++] = x;
	}

	int Top()
	{
		return a[top - 1];
	}

	void Pop()
	{
		assert(top > 0);
		--top;
	}

	void Destroy()
	{
		free(a);
		a = nullptr;
		top = capacity = 0;
	}

	bool Empty()
	{
		return top == 0;
	}
private:
	// 成员变量
	int* a;
	int top;
	int capacity;
};

// 两个栈实现一个队列
class MyQueue
{
private:
	Stack _pushst;
	Stack _popst;
};
int main()
{
	Date d1;
	Date d2;

	Stack st1;
	Stack st2;
	return 0;
}

自从学了C++之后,小雅兰就有对象了!!!(类与对象)(中)——“C++”,C++,c++,开发语言,c语言,数据结构,算法

 其实日期类不需要写析构函数!!!

 像栈这样的数据结构,就需要写析构函数!!!

自从学了C++之后,小雅兰就有对象了!!!(类与对象)(中)——“C++”,C++,c++,开发语言,c语言,数据结构,算法

关于编译器自动生成的析构函数,是否会完成一些事情呢?下面的程序我们会看到,编译器 生成的默认析构函数,对自定类型成员调用它的析构函数。

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

Date类;有资源申请时,一定要写,否则会造成资源泄漏,比如Stack类。

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

class Date
{
private:
	// 基本类型(内置类型)
	int _year = 1970;
	int _month = 1;
	int _day = 1;

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

int main()
{
	Date d;
	return 0;
}

// 程序运行结束后输出:~Time()
// 在main函数中根本没有直接创建Time类的对象,为什么最后会调用Time类的析构函数?
// 因为:main方法中创建了Date对象d,而d中包含4个成员变量,其中_year, _month, _day三个是
// 内置类型成员,销毁时不需要资源清理,最后系统直接将其内存回收即可;而_t是Time类对象,所以在
// d销毁时,要将其内部包含的Time类的_t对象销毁,所以要调用Time类的析构函数。但是:main函数
// 中不能直接调用Time类的析构函数,实际要释放的是Date类对象,所以编译器会调用Date类的析构函
// 数,而Date没有显式提供,则编译器会给Date类生成一个默认的析构函数,目的是在其内部调用Time
// 类的析构函数,即当Date对象销毁时,要保证其内部每个自定义对象都可以正确销毁
// main函数中并没有直接调用Time类析构函数,而是显式调用编译器为Date类生成的默认析构函数
// 注意:创建哪个类的对象则调用该类的析构函数,销毁那个类的对象则调用该类的析构函数

自从学了C++之后,小雅兰就有对象了!!!(类与对象)(中)——“C++”,C++,c++,开发语言,c语言,数据结构,算法


拷贝构造函数

概念

在现实生活中,可能存在一个与你一样的自己,我们称其为双胞胎。

自从学了C++之后,小雅兰就有对象了!!!(类与对象)(中)——“C++”,C++,c++,开发语言,c语言,数据结构,算法

那在创建对象时,可否创建一个与已存在对象一模一样的新对象呢?  

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

特征

拷贝构造函数也是特殊的成员函数,其特征如下:

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

自从学了C++之后,小雅兰就有对象了!!!(类与对象)(中)——“C++”,C++,c++,开发语言,c语言,数据结构,算法

class Date
{
public:
	Date(int year = 1900, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	// Date(const Date& d)// 正确写法
	Date(const Date d)// 错误写法:编译报错,会引发无穷递归
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}
private:
	int _year;
	int _month;
	int _day;
};

int main()
{
	Date d1;
	Date d2(d1);
	return 0;
}

自从学了C++之后,小雅兰就有对象了!!!(类与对象)(中)——“C++”,C++,c++,开发语言,c语言,数据结构,算法

 自从学了C++之后,小雅兰就有对象了!!!(类与对象)(中)——“C++”,C++,c++,开发语言,c语言,数据结构,算法

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

	Date(Date& d)
	{
		cout << "Date(Date& d)" << endl;

		_year = d._year;
		_month = d._month;
		_day = d._day;
	}

	void Print()
	{
		cout << _year << "/" << _month << "/" << _day << endl;
	}
	
private:
	// 内置类型
	int _year;
	int _month;
	int _day;
};

typedef int DataType;
class Stack
{
public:
	Stack(size_t capacity = 3)
	{
		_array = (DataType*)malloc(sizeof(DataType) * capacity);
		if (NULL == _array)
		{
			perror("malloc申请空间失败!!!");
			return;
		}

		_capacity = capacity;
		_size = 0;
	}

	Stack(Stack& s)
	{
		cout << "Stack(Stack& s)" << endl;
		// 深拷贝
		_array = (DataType*)malloc(sizeof(DataType) * s._capacity);
		if (NULL == _array)
		{
			perror("malloc申请空间失败!!!");
			return;
		}

		memcpy(_array, s._array, sizeof(DataType) * s._size);
		_size = s._size;
		_capacity = s._capacity;
	}

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

	~Stack()
	{
		cout << "~Stack()" << endl;
		free(_array);
		_array = nullptr;
		_size = _capacity = 0;
	}
private:
	// 内置类型
	DataType* _array;
	int _capacity;
	int _size;
};

void func1(Date d)
{
	d.Print();
}
// 期望呢,s要插入一些数据,s的改变,不影响s1
void func2(Stack s)
{
	s.Push(1);
	s.Push(2);
}

int main()
{
	Date d1(2023, 7, 21);
	func1(d1);

	Stack s1;
	func2(s1);

	Stack s2(s1);

	// 以下两个写法是等价的,都是拷贝构造
	Date d2(d1);
	Date d3 = d1;


	return 0;
}

自从学了C++之后,小雅兰就有对象了!!!(类与对象)(中)——“C++”,C++,c++,开发语言,c语言,数据结构,算法

自从学了C++之后,小雅兰就有对象了!!!(类与对象)(中)——“C++”,C++,c++,开发语言,c语言,数据结构,算法

若未显式定义,编译器会生成默认的拷贝构造函数。 默认的拷贝构造函数对象按内存存储按 字节序完成拷贝,这种拷贝叫做浅拷贝,或者值拷贝。

class Time
{
public:
	Time()
	{
		_hour = 1;
		_minute = 1;
		_second = 1;
	}
	Time(const Time& t)
	{
		_hour = t._hour;
		_minute = t._minute;
		_second = t._second;
		cout << "Time::Time(const Time&)" << endl;
	}
private:
	int _hour;
	int _minute;
	int _second;
};

class Date
{
private:
	// 基本类型(内置类型)
	int _year = 1970;
	int _month = 1;
	int _day = 1;

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

int main()
{
	Date d1;
	// 用已经存在的d1拷贝构造d2,此处会调用Date类的拷贝构造函数
	// 但Date类并没有显式定义拷贝构造函数,则编译器会给Date类生成一个默认的拷贝构造函数
	Date d2(d1);
	return 0;
}

自从学了C++之后,小雅兰就有对象了!!!(类与对象)(中)——“C++”,C++,c++,开发语言,c语言,数据结构,算法

 注意:在编译器生成的默认拷贝构造函数中,内置类型是按照字节方式直接拷贝的,而自定 义类型是调用其拷贝构造函数完成拷贝的。

我们不写,编译默认生成的拷贝构造,跟之前的构造函数特性不一样

  • 内置类型, 值拷贝
  • 自定义的类型,调用它的拷贝 

总结:Date不需要我们实现拷贝构造,默认生成就可以用

        Stack需要我们自己实现深拷贝的拷贝构造,默认生成会出问题

MyQueue对于默认生成的几个函数非常受用,人生赢家

class MyQueue
{
private:
    Stack _pushst;
    Stack _popst;
};

MyQueue mq1;
MyQueue mq2 = mq1;

自从学了C++之后,小雅兰就有对象了!!!(类与对象)(中)——“C++”,C++,c++,开发语言,c语言,数据结构,算法

编译器生成的默认拷贝构造函数已经可以完成字节序的值拷贝了,还需要自己显式实现吗? 当然像日期类这样的类是没必要的。那么下面的类呢?验证一下试试?

// 这里会发现下面的程序会崩溃掉?这里就需要我们以后学的深拷贝去解决。
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)
	{
		// CheckCapacity();
		_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;
}

自从学了C++之后,小雅兰就有对象了!!!(类与对象)(中)——“C++”,C++,c++,开发语言,c语言,数据结构,算法

自从学了C++之后,小雅兰就有对象了!!!(类与对象)(中)——“C++”,C++,c++,开发语言,c语言,数据结构,算法 

注意:类中如果没有涉及资源申请时,拷贝构造函数是否写都可以;一旦涉及到资源申请 时,则拷贝构造函数是一定要写的,否则就是浅拷贝。  

拷贝构造函数典型调用场景:

  1. 使用已存在对象创建新对象
  2. 函数参数类型为类类型对象
  3. 函数返回值类型为类类型对象  
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(2022, 1, 13);
	Test(d1);
	return 0;
}

自从学了C++之后,小雅兰就有对象了!!!(类与对象)(中)——“C++”,C++,c++,开发语言,c语言,数据结构,算法

为了提高程序效率,一般对象传参时,尽量使用引用类型,返回时根据实际场景,能用引用尽量使用引用。  


所有源代码如下:

#include<iostream>
#include<assert.h>
using namespace std;
class Date
{
public:
    void Init(int year, int month, int day)
    {
        _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(2023, 8, 9);
    d1.Print();
    Date d2;
    d2.Init(2023, 8, 10);
    d2.Print();
    return 0;
}

 

class Date
{
public:
    无参构造函数
    //Date()
    //{
    //    cout << "Date()" << endl;
    //    _year = 1;
    //    _month = 1;
    //    _day = 1;
    //}
    带参构造函数
    //Date(int year, int month, int day)
    //{
    //    cout << "Date(int year, int month, int day)" << endl;
    //    _year = year;
    //    _month = month;
    //    _day = day;
    //}
    //带参构造函数
    Date(int year = 1, int month = 1, int day = 1)
    {
        cout << "Date(int year, int month, int day)" << endl;
        _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.Print();
    // 调用带参的构造函数
    Date d2(2023, 8, 9);
    d2.Print();
    // 注意:如果通过无参构造函数创建对象时,对象后面不用跟括号,否则就成了函数声明
    // 以下代码的函数:声明了d3函数,该函数无参,返回一个日期类型的对象
    // warning C4930: “Date d3(void)”: 未调用原型函数(是否是有意用变量定义的?)
    //Date d3();
    //d3.Print();
    Date d3(2023);
    d3.Print();
    Date d4(2023, 8);
    d4.Print();
    return 0;
}

class Stack
{
public:
    Stack(size_t n = 4)
    {
        if (n == 0)
        {
            a = nullptr;
            top = capacity = 0;
        }
        else
        {
            a = (int*)malloc(sizeof(int) * n);
            if(a == nullptr)
            {
                perror("realloc fail");
                exit(-1);
            }

            top = 0;
            capacity = n;
        }
    }
    void Push(int x)
    {
        if (top == capacity)
        {
            size_t newcapacity = capacity == 0 ? 4 : capacity * 2;
            int* tmp = (int*)realloc(a, sizeof(int) * newcapacity);
            if (tmp == nullptr)
            {
                perror("realloc fail");
                exit(-1);
            }
            if (tmp == a)
            {
                cout << capacity << "原地扩容" << endl;
            }
            else
            {
                cout << capacity << "异地扩容" << endl;
            }

            a = tmp;
            capacity = newcapacity;
        }

        a[top++] = x;
    }

    int Top()
    {
        return a[top - 1];
    }

    void Pop()
    {
        assert(top > 0);
        --top;
    }

    void Destroy()
    {
        free(a);
        a = nullptr;
        top = capacity = 0;
    }

    bool Empty()
    {
        return top == 0;
    }
private:
    // 成员变量
    int* a;
    int top;
    int capacity;
};

int main()
{
    Stack st1;
    st1.Push(1);
    st1.Push(2);
    st1.Push(3);
    st1.Push(4);
    
    while (!st1.Empty())
    {
        cout << st1.Top() << " ";
        st1.Pop();
    }
    cout << endl;

    st1.Destroy();

    Stack st2(1000);
    //Stack st2;
    for (size_t i = 0; i < 1000; i++)
    {
        st2.Push(i);
    }

    while (!st2.Empty())
    {
        cout << st2.Top() << " ";
        st2.Pop();
    }
    cout << endl;

    st2.Destroy();
    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;
    int _month;
    int _day;
};

int main()
{
    // 将Date类中构造函数屏蔽后,代码可以通过编译,因为编译器生成了一个无参的默认构造函数
    // 将Date类中构造函数放开,代码编译失败,因为一旦显式定义任何构造函数,编译器将不再生成
    // 无参构造函数,放开后报错:error C2512: “Date”: 没有合适的默认构造函数可用
    Date d1;
    d1.Print();
    return 0;
}

// 构造函数,也是默认成员函数,我们不写,编译器会自动生成
// 编译生成的默认构造的特点:
// 1、我们不写才会生成,我们写了任意一个构造函数就不会生成了
// 2、内置类型的成员不会处理(C++11,声明支持给缺省值)
// 3、自定义类型的成员才会处理,回去调用这个成员的默认构造函数

// 总结:一般情况都需要我们自己写构造函数,决定初始化方式
// 成员变量全是自定义类型,可以考虑不写构造函数

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

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

class Date
{
private:
    // 基本类型(内置类型)
    int _year;
    int _month;
    int _day;
    // 自定义类型
    Time _t;
};

int main()
{
    Date d;
    return 0;
}

 

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

class Date
{
private:
    // 基本类型(内置类型)
    int _year = 1970;
    int _month = 1;
    int _day = 1;
    // 自定义类型
    Time _t;
};

int main()
{
    Date d;
    return 0;
}

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

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

// 以下测试函数能通过编译吗?
void Test()
{
    Date d1;
}
int main()
{
    Test();
    return 0;
}

 

class Date
{
public:
    Date(int year = 1, int month = 1, int day = 1)
    {
        cout << "Date(int year = 1, int month = 1, int day = 1)" << endl;

        _year = year;
        _month = month;
        _day = day;
    }
    void Print()
    {
        cout << _year << "/" << _month << "/" << _day << endl;
    }

    ~Date()
    {
        cout << "~Date()" << endl;
    }

private:
    int _year = 1;   // 声明给的缺省值
    int _month = 1;
    int _day = 1;
};

class Stack
{
public:
    Stack(size_t n = 4)
    {
        cout << "Stack(size_t n = 4)" << endl;

        if (n == 0)
        {
            a = nullptr;
            top = capacity = 0;
        }
        else
        {
            a = (int*)malloc(sizeof(int) * n);
            if (a == nullptr)
            {
                perror("realloc fail");
                exit(-1);
            }

            top = 0;
            capacity = n;
        }
    }

    ~Stack()
    {
        cout << "~Stack()" << endl;
        free(a);
        a = nullptr;
        top = capacity = 0;
    }

    void Push(int x)
    {
        if (top == capacity)
        {
            size_t newcapacity = capacity == 0 ? 4 : capacity * 2;
            int* tmp = (int*)realloc(a, sizeof(int) * newcapacity);
            if (tmp == nullptr)
            {
                perror("realloc fail");
                exit(-1);
            }
            if (tmp == a)
            {
                cout << capacity << "原地扩容" << endl;
            }
            else
            {
                cout << capacity << "异地扩容" << endl;
            }

            a = tmp;
            capacity = newcapacity;
        }

        a[top++] = x;
    }

    int Top()
    {
        return a[top - 1];
    }

    void Pop()
    {
        assert(top > 0);
        --top;
    }

    void Destroy()
    {
        free(a);
        a = nullptr;
        top = capacity = 0;
    }

    bool Empty()
    {
        return top == 0;
    }
private:
    // 成员变量
    int* a;
    int top;
    int capacity;
};

// 两个栈实现一个队列
class MyQueue
{
private:
    Stack _pushst;
    Stack _popst;
};
int main()
{
    Date d1;
    Date d2;

    Stack st1;
    Stack st2;
    return 0;
}

 

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

class Date
{
private:
    // 基本类型(内置类型)
    int _year = 1970;
    int _month = 1;
    int _day = 1;

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

int main()
{
    Date d;
    return 0;
}

// 程序运行结束后输出:~Time()
// 在main函数中根本没有直接创建Time类的对象,为什么最后会调用Time类的析构函数?
// 因为:main方法中创建了Date对象d,而d中包含4个成员变量,其中_year, _month, _day三个是
// 内置类型成员,销毁时不需要资源清理,最后系统直接将其内存回收即可;而_t是Time类对象,所以在
// d销毁时,要将其内部包含的Time类的_t对象销毁,所以要调用Time类的析构函数。但是:main函数
// 中不能直接调用Time类的析构函数,实际要释放的是Date类对象,所以编译器会调用Date类的析构函
// 数,而Date没有显式提供,则编译器会给Date类生成一个默认的析构函数,目的是在其内部调用Time
// 类的析构函数,即当Date对象销毁时,要保证其内部每个自定义对象都可以正确销毁
// main函数中并没有直接调用Time类析构函数,而是显式调用编译器为Date类生成的默认析构函数
// 注意:创建哪个类的对象则调用该类的析构函数,销毁那个类的对象则调用该类的析构函数
 

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

    Date(Date& d)
    {
        cout << "Date(Date& d)" << endl;

        _year = d._year;
        _month = d._month;
        _day = d._day;
    }

    void Print()
    {
        cout << _year << "/" << _month << "/" << _day << endl;
    }
    
private:
    // 内置类型
    int _year;
    int _month;
    int _day;
};

typedef int DataType;
class Stack
{
public:
    Stack(size_t capacity = 3)
    {
        _array = (DataType*)malloc(sizeof(DataType) * capacity);
        if (NULL == _array)
        {
            perror("malloc申请空间失败!!!");
            return;
        }

        _capacity = capacity;
        _size = 0;
    }

    Stack(Stack& s)
    {
        cout << "Stack(Stack& s)" << endl;
        // 深拷贝
        _array = (DataType*)malloc(sizeof(DataType) * s._capacity);
        if (NULL == _array)
        {
            perror("malloc申请空间失败!!!");
            return;
        }

        memcpy(_array, s._array, sizeof(DataType) * s._size);
        _size = s._size;
        _capacity = s._capacity;
    }

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

    ~Stack()
    {
        cout << "~Stack()" << endl;
        free(_array);
        _array = nullptr;
        _size = _capacity = 0;
    }
private:
    // 内置类型
    DataType* _array;
    int _capacity;
    int _size;
};

void func1(Date d)
{
    d.Print();
}
// 期望呢,s要插入一些数据,s的改变,不影响s1
void func2(Stack s)
{
    s.Push(1);
    s.Push(2);
}

int main()
{
    Date d1(2023, 7, 21);
    func1(d1);

    Stack s1;
    func2(s1);

    Stack s2(s1);

    // 以下两个写法是等价的,都是拷贝构造
    Date d2(d1);
    Date d3 = d1;


    return 0;
}

class Date
{
public:
    Date(int year = 1900, int month = 1, int day = 1)
    {
        _year = year;
        _month = month;
        _day = day;
    }
    // Date(const Date& d)// 正确写法
    Date(const Date& d)// 错误写法:编译报错,会引发无穷递归
    {
        _year = d._year;
        _month = d._month;
        _day = d._day;
    }
private:
    int _year;
    int _month;
    int _day;
};

int main()
{
    Date d1;
    Date d2(d1);
    return 0;
}

 

class Time
{
public:
    Time()
    {
        _hour = 1;
        _minute = 1;
        _second = 1;
    }
    Time(const Time& t)
    {
        _hour = t._hour;
        _minute = t._minute;
        _second = t._second;
        cout << "Time::Time(const Time&)" << endl;
    }
private:
    int _hour;
    int _minute;
    int _second;
};

class Date
{
private:
    // 基本类型(内置类型)
    int _year = 1970;
    int _month = 1;
    int _day = 1;

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

int main()
{
    Date d1;
    // 用已经存在的d1拷贝构造d2,此处会调用Date类的拷贝构造函数
    // 但Date类并没有显式定义拷贝构造函数,则编译器会给Date类生成一个默认的拷贝构造函数
    Date d2(d1);
    return 0;
}

// 这里会发现下面的程序会崩溃掉?这里就需要我们以后学的深拷贝去解决。
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)
    {
        // CheckCapacity();
        _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(2022, 1, 13);
    Test(d1);
    return 0;
}

 


 好啦,小雅兰今天的学习内容就到这里啦,还要继续加油噢!!!!

自从学了C++之后,小雅兰就有对象了!!!(类与对象)(中)——“C++”,C++,c++,开发语言,c语言,数据结构,算法文章来源地址https://www.toymoban.com/news/detail-638144.html

到了这里,关于自从学了C++之后,小雅兰就有对象了!!!(类与对象)(中)——“C++”的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【C++】类与对象(上)

    之前的文章中讲解了,什么是类、类的实例化,以及封装的意义,若仍有不理解的部分可以移步上一篇文章  【C++】类与对象(引入) 目录 系列文章 1.默认成员函数 2.构造函数 2.1定义 2.2特性 2.2.1重载构造函数 2.2.2与缺省参数混合使用 2.2.3默认构造函数 3.析构函数 3.1定义

    2023年04月15日
    浏览(44)
  • 【C++】——类与对象(上)

    今天我们来学习C++初期最重要的知识点,类与对象,因为类与对象涉及的知识太多,所以我将分为上中下三个部分来为大家讲解类与对象。 C语言是面向过程的,关注的是过程,分析出求解问题的步骤,通过函数调用逐步解决问题。 C++是基于面向对象的,关注的是对象,将一

    2024年02月02日
    浏览(41)
  • C++:类与对象

    类是对一组具有共同属性特征和行为特征的对象的抽象,它可将相关数据和这些数据的操作(函数)组合在一起。 类定义格式: 类名 : class是声明类的,类名是标识符,且在其作用域内必须是唯一的。 注:C++规定,标识符以字母(大小写均可,但区分大小写)或下划

    2024年02月12日
    浏览(44)
  • C++ 类与对象(上)

    目录 本节目标  1.面向过程和面向对象初步认识  2.类的引入  3.类的定义 4.类的访问限定符及封装 4.1 访问限定符 4.2 封装 5. 类的作用域 6. 类的实例化  7.类对象模型 7.1 如何计算类对象的大小 7.2 类对象的存储方式猜测 7.3 结构体内存对齐规则  8.this指针 8.1 this指针的引出

    2024年02月19日
    浏览(43)
  • 【C++】类与对象(中)

    目录 1. 类的6个默认成员函数 2. 构造函数 2.1 概念 2.2 特性 3. 析构函数 3.1 概念 3.2 特性 4. 拷贝构造函数 4.1 概念 4.2 特征 5. 赋值运算符重载 5.1 运算符重载 5.2 赋值运算符重载 5.3 前置++和后置++重载 6. const成员 7. 取地址及const取地址操作符重载 如果一个类中什么成员都没有,

    2024年02月05日
    浏览(47)
  • C++ 类与对象(下)

    目录 1. 再谈构造函数 1.1 构造函数体赋值  1.2 初始化列表  1.3 explicit  2. static成员 2.1 概念 2.2 特性  3.友元 3.1友元函数   3.2 友元类 4. 内部类 5.匿名对象  6.拷贝对象时的一些编译器优化  7. 再次理解类和对象 1. 再谈构造函数 2. Static成员 3. 友元 4. 内部类 5.匿名对象

    2024年02月19日
    浏览(40)
  • C++类与对象(下)

    创建对象时,编译器会调用构造函数给对象中的成员变量一个合适的初始值。 虽然上述构造函数调用时,每个成员变量都有一个初始值了,但是这并不能称为类对象成员的初始化, 构造函数体中的语句只能称为赋初值 ,而不能称为初始化,因为 初始化只能初始一次,而构造

    2024年02月11日
    浏览(40)
  • [C++] 类与对象(上)

      目录 1、前言 2、类的引入 3、类的定义 3.1 类的两种定义方式 4、类的访问限定符 5、类的作用域 6、类的实例化 7、类对象模型 7.1 内存对齐规则 7.1 类对象的存储方式 8、this指针 8.1 this指针的特性 8.2 this指针是否可以为空 C语言是 面向过程 的, 关注 的是 过程 ,分析出求解

    2024年02月15日
    浏览(38)
  • 【C++】——类与对象(下)

    本文是C++类与对象部分最后一篇文章,类与对象的重点是类与对象(中),本文主要是补充类与对象的剩余知识点及收尾。 在创建对象时,编译器通过调用构造函数,给对象中各个成员变量一个合适的初始值。 构造函数体中的语句只能将其称为赋初值,而不能称作初始化。

    2024年02月06日
    浏览(33)
  • [C++]类与对象下篇

    目录 类与对象下篇::                             1.再谈构造函数                             2.static成员                             3.友元                             4.内部类                             5.匿名对象                             6.拷贝

    2023年04月25日
    浏览(44)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包