C++初阶类和对象

Posted Huang_ZhenSheng

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C++初阶类和对象相关的知识,希望对你有一定的参考价值。

目录

一,构造函数 

第一点:

第二点:

第三点:

第四点:

​第五点:

二,析构函数

三,拷贝构造函数

四,赋值运算符重载

1,运算符重载

第一部分:

第二部分:日期类的实现(详见下一章)

2,赋值运算符重载


一,构造函数 

下面先看一段代码:

class Date
{
public:
	void Init(int year, int month, int day)
	{
		_year = year;
		_month = month;
		_day = day;
	}
private:
	int _year;
	int _month;
	int _day;
};
class Stack
{
public:
	void Init()
	{
		_a = nullptr;
		_top = _capacity = 0;
	}
private:
	int* _a;
	int _top;
	int _capacity;
};
int main()
{
	Date d1;
	d1.Init(2021,10,9);
	Stack s1;
	s1.Init();
	return 0;
}

经常会忘记调用Init函数

能否做到对象定义出来就初始化了呢?

————>构造函数,在对象定义的时候就去调用

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

第一点:

1. 函数名与类名相同

2. 无返回值

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

4. 构造函数可以重载

函数重载:可以提供多种初始化对象的方式

例如:

class Date
{
public:
	void Init(int year, int month, int day)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	Date()
	{
		_year = 0;
		_month = 1;
		_day = 1;
	}
	Date(int year,int month,int day)
	{
		_year = year ;
		_month = month ;
		_day = day ;
	}
private:
	int _year;
	int _month;
	int _day;
};
class Stack
{
public:
	void Init()
	{
		_a = nullptr;
		_top = _capacity = 0;
	}
	Stack()
	{
		_a = nullptr;
		_top = _capacity = 0;
	}
	Stack(int capacity)
	{
		_a = (int*)malloc(sizeof(int)*capacity);
		if (_a == nullptr)
		{
			cout << "malloc fail" << endl;
			exit(-1);
		}
		_top = 0;
		_capacity = capacity;
	}
private:
	int* _a;
	int _top;
	int _capacity;
};
int main()
{
	Date d1;
	Date d2(2021,10,9);
	//d1.Init(2021,10,9);
	Stack s1;
	Stack s2(10);
	//s1.Init();
	return 0;
}

第二点:

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

 我们不写构造函数,编译器默认生成构造函数,做了一个偏心的处理

1.对于内置类型(int,char,double,指针)不会初始化

2.对于自定义类型(struct/class),会调用它的构造函数初始化

例如: 

class A
{
public:
	A()
	{
		_a1 = 0;
		_a2 = 1;
	}
private:
	int _a1;
	int _a2;
};
class Date
{
public:
	void Print()
	{
		cout << _year << "/" << _month << "/" << _day << endl;
	}
private:
	//内置类型/基本类型    int,char,double,指针......
	int _year;
	int _month;
	int _day;
	//自定义类型
	A _aa;
};
int main()
{
	Date d1;
	d1.Print();
	return 0;
}

 自定义类型它会调用它的无参构造函数初始化

下面继续看一段代码: 

class A
{
public:
	A(int a)
	{
		_a1 = 0;
		_a2 = 1;
		cout << "A()" << endl;
	}
private:
	int _a1;
	int _a2;
};
class Date
{
public:
	void Print()
	{
		cout << _year << "/" << _month << "/" << _day << endl;
	}
private:
	//内置类型/基本类型    int,char,double,指针......
	int _year;
	int _month;
	int _day;
	//自定义类型
		A _aa;
};
int main()
{
	Date d1;
	d1.Print();
	return 0;
}

C++的时候,语法委员会为这里打了一个补丁

 成员变量只是定义,这里不是初始化,这里就像函数缺省参数一样,给的是缺省值

第三点:

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

默认构造函数:不传参数就可以调用的那个,无参或者全缺省的都可以叫做默认构造函数

下面这段代码 本来是构成函数重载的,但是他们两个不能同时存在,Date d会出现歧义,编译器不知道调谁了

class Date
{
public:
	//本来是构成函数重载的
	//但是他们两不能同时存在!!!
	Date()
	{
		_year = 0;
		_month = 1;
		_day = 1;
	}
	Date(int year = 0,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;
	Date(1, 1, 1);
	return 0;
}

 一般情况下推荐全缺省的构造函数

第四点:

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

————>

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

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;
}

第五点:

 以上这段代码的year为随机值,局部变量优先,自己给自己,这个变量压根没动到

改成下面这样就可以通过

二,析构函数

析构函数是特殊的成员函数

1. 析构函数名是在类名前加上字符 ~。
2. 无参数无返回值。
3. 一个类有且只有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。
4. 对象生命周期结束时,C++编译系统系统自动调用析构函数。

class Date
{
public:
	Date(int year = 1,int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	void Print()
	{
		cout << _year << "/" << _month << "/" << _day << endl;
	}
	//像Date这样的类是不需要析构函数的,因为他内部没有什么资源需要清理
	~Date()
	{
		cout << "~Date()" << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};
class Stack
{
public:
	Stack(int capacity = 4)
	{
		_a = (int*)malloc(sizeof(int)*capacity);
		if (_a == nullptr)
		{
			cout << "malloc fail" << endl;
		}
		_top = 0;
		_capacity = capacity;
	}
	~Stack()
	{
		//像Stack这样的类,对象中的资源需要清理,就用析构函数
		free(_a);
		_a = nullptr;
		_top = _capacity = 0;
	}
private:
	int* _a;
	int _top;
	int _capacity;
};
int main()
{
	Date d1;
	Stack st;
	return 0;
}

关于编译器自动生成析构函数,编译器生成的默认析构函数,对会自定类型成员调用它析构函数。

三,拷贝构造函数

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

特征:

1. 拷贝构造函数是构造函数的一个重载形式
2. 拷贝构造函数的参数只有一个且必须使用引用传参,使用传值方式会引发无穷递归调用。

class Date
{
public:
	Date(int year, int month, int day)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	Date(const Date& d)
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}
	void Print()
	{
		cout << _year << "/" << _month << "/" << _day << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};
int main()
{
	Date d1(2021,10,9);
	Date d2(d1);
	d2.Print();
	return 0;
}

 如果引用传参,不是做输出型参数,最好用const & 做保护

 我们不写,编译器会默认生成拷贝构造,跟构造和析构又不太一样

不会去区分内置类型和自定义类型成员,都会处理

 1,内置类型,字节序的浅拷贝

2,自定义类型,会去调用他的拷贝构造完成拷贝

那么编译器生成的默认拷贝构造函数已经可以完成字节序的值拷贝了(内置类型和自定义类型,默认生成的拷贝构造都处理了),是否意味着,编译器自己生成就OK呢?

class Stack
{
public:
	Stack(int capacity = 4)
	{
		_a = (int*)malloc(sizeof(int)*capacity);
		if (_a == nullptr)
		{
			cout << "malloc fail" << endl;
			exit(-1);
		}
		_top = 0;
		_capacity = capacity;
	}
	~Stack()
	{
		free(_a);
		_a = nullptr;
		_top = _capacity = 0;
	}
private:
	int* _a;
	int _top;
	int _capacity;
};
int main()
{
	Stack st1;
	Stack st2(st1);
	return 0;
}

上面这段代码崩了?why?

在进行析构的时候,st2先析构, 把空间给释放了,将_a给置成了NULL,_top置为0,_capacity置为0,free(a)崩溃(同一个空间不能释放两次),导致同一块空间释放两次

编译器默认生成的拷贝构造并不能解决所以的问题,像Stack这样类,编译器默认生成拷贝构造完成的就是浅拷贝。

解决方案:自己实现深拷贝拷贝构造函数!

四,赋值运算符重载

1,运算符重载

第一部分:

运算符默认都是内置类型变量用的,自定义类型的变量想用这些运算符,得自己进行运算符重载(即自己去写一个函数定义实现这里运算符的行为)

函数:operate 运算符

参数:操作符的操作数有几个就有几个参数,参数类型你要操作的对象类型

返回值:看运算符运算后的返回值是什么

看下面这段代码:

class Date
{
public:
	Date(int year=0, int month=1, int day=1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	//运算符重载:
	bool operator == (const Date& x1, const Date& x2)
	{
		return x1._year == x2._year
			&& x1._month == x2._month
			&& x1._day == x2._day;
	}
private:
	int _year;
	int _month;
	int _day;
};
int main()
{
	Date d1(2021, 11, 11);
	Date d2(2020, 11, 11);
	Date d3(2021, 11, 11);
	d1 == d2;//转换成operator ==(d1,d2)
	return 0;
}

--------> 运行后出现如下问题:

d1==d2 —>  d1.operator == (d2)   —>  d1.operator ==(&d1,d2)

bool operator == (Date*this,const Date& x)

d1传给了this,d2传给了x

class Date
{
public:
	Date(int year=0, int month=1, int day=1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	//运算符重载:
	//d1==d2 —>d1.operator == (d2)—>d1.operator ==(&d1,d2)
	//bool operator == (Date*this,const Date& x)//d1传给了this,d2传给了x
	bool operator == (const Date& x)
	{
		return this->_year == x._year
			&& this->_month == x._month
			&& this->_day == x._day;
	}
private:
	int _year;
	int _month;
	int _day;
};
int main()
{
	Date d1(2021, 11, 11);
	Date d2(2020, 11, 11);
	Date d3(2021, 11, 11);
	d1.operator == (d2);
	d1 == d2;//转换成operator ==(d1,d2)
	return 0;
}

下面实现一下d1<d2的: 

class Date
{
public:
	Date(int year = 0, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	bool operator < (const Date& x)
	{
		if (_year < x._year)
			return true;
		else if (_year == x._year && _month < x._month)
			return true;
		else if (_year == x._year && _month == x._month && _day < x._day)
			return true;
		else
			return false;
	}
private:
	int _year;
	int _month;
	int _day;
};
int main()
{
	Date d1(2021, 11, 11);
	Date d2(2020, 11, 11);
	Date d3(2021, 11, 11);
	d1 < d2;
	cout << (d2 < d3) << endl;
	cout << (d1 < d2) << endl;
	return 0;
}

第二部分:日期类的实现(详见下一章)

2,赋值运算符重载

赋值运算符重载,用于两个已经定义出来的对象间拷贝复制

class Date
{
public:
	Date(int year = 0, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	Date& operator = (const Date& d)
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;

		return *this;
	}
	void print()
	{
		cout << _year << "/" << _month << "/" << _day << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};
int main()
{
	Date d1(2021, 10, 11);
	Date d2(2020, 11, 11);
	Date d3(2021, 11, 11);
	//赋值运算符重载,用于两个已经定义出来的对象间拷贝复制
	d1 = d2 = d3;
	d1.print();
	d3.print();
	return 0;
}

如果我们不写,编译器会默认生成赋值重载

跟拷贝构造的行为类似,内置类型成员会完成值拷贝,自定义类型成员会调用它的赋值重载

小结:

对于日期类:自己写构造函数就可以,其他编译器生成就可以用

对于栈:构造函数,析构函数,拷贝构造,赋值重载都需要我们自己写,默认生成都有问题,不能用

第一:C++为了增强程序的可读性,提出运算符重载,并且让我们可以实现运算符重载函数控制,这个运算符行为

第二:一个类到底要重载哪些运算符,是看你需要哪些运算符,并且考量重载了这个运算符有没有意义

第三:运算符重载和函数重载,都用了重载这个词,但是他们之前没有关联

以上是关于C++初阶类和对象的主要内容,如果未能解决你的问题,请参考以下文章

C++ 初阶类和对象

C++ 初阶类和对象

C++初阶类和对象

C++初阶类和对象

C++初阶---类和对象

C++初阶第四篇——类和对象(上)(类的定义+封装+this指针)