C++类与对象详解

类与对象详解(下)

首先在这里感谢各位的大佬的阅读 如有不合理的地方 请各位大佬及时指出 蟹蟹!

内容:
1.类的6个默认成员
1.1 构造函数
1.2 析构函数
1.3 拷贝构造函数
1.4 赋值操作符重载
1.5 const成员函数
1.6 &及const&操作符重载
1.7 static成员
1.8 友元
1.9 内部类

正文

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

先定义一个类

class Date
{};

如果一个类中什么成员都没有,如上代码,简称为空类。那么空类中真的什么都没有吗?其实并不是这样的,任何一个类在我们不显示定义的情况下,都会自动生成下面6个默认成员函数。

1.1构造函数

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

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

  1. 函数名与类名相同。
  2. 无返回值。
  3. 对象实例化是编译器自动调用对应的构造函数。
  4. 构造函数可以重载。
    看代码
class Date
{
public:
	Date()
	{
		cout << "Date():" << this << endl;
	}

	Date(int year, int month, int day)
	{
		_year = year;
		_month = month;
		_day = day;
	}

	void InitDate(int year, int month, int day)
	{
		_year = year;
		_month = month;
		_day = day;
	}

	void PrintDate()
	{
		cout << _year << "/" << _month << "/" << _day << endl;
	}

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


int main()
{

	Date d1, d2, d3;//调用无参构造函数
	Date d5(2019, 3, 19);//调用带参的构造函数
	// Date d4(); 不是创建了一个对象,而是一个函数声明(没有参数,返回一个日期类型对象的函数声明)
	d1.InitDate(2019, 3, 19);
	d1.PrintDate();

	d2.InitDate(2019, 3, 20);
	d2.PrintDate();

	d3.InitDate(2019, 3, 18);
	d3.PrintDate();

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

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

class Date
{
public:
	// 默认构造函数
	
	// 无参构造函数
	 //	Date()
	 //	{
	 //		cout << "Date():" << this << endl;
	 //	}

	// 全缺省的构造函数
	Date(int year = 1900, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}


	void PrintDate()
	{
		cout << _year << "/" << _month << "/" << _day << endl;
	}

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


int main()
{
	// 默认的构造函数只能存在一个
	Date d1;
	//Date d2(2019, 3, 19);
	return 0;
}

如果这里将无参构造函数放开,运行到 Date d1时编译器会报错。因为这里的 Date d1 不知道要调用哪一个构造函数。

  1. . 关于编译器生成的默认成员函数,很多童鞋会有疑惑:在我们不实现构造函数的情况下,编译器会生成默认的构造函数。但是看起来默认构造函数又没什么用?
    其实并不然,编译器会在感觉自己需要的时候才会生成默认的构造函数。
    比如如下场景:
class Time
{
public:
	Time(int hour = 0, int minute = 0, int second = 0)
	{
		cout << this->_hour << endl;
	}

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

class Date
{
public:
	//编译器自动生成默认构造函数

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

int main()
{
	Date d; // 必须调用Time() --->找一个调用位置--->日期类的构造函数 
	
	return 0;
}

上面的场景中,要运行Date d;语句,编译器一定会生成Date类的默认构造函数。

构造函数体中的语句只能将其称作为赋初值,而不能称作初始化。因为初始化只能初始化一次,而构造函数体内可以多次赋值。

8.初始化列表
初始化列表:以一个冒号开始,接着是一个以逗号分隔的数据成员列表,每个"成员变量"后面跟一个放在括号中的初始值或表达式。
看代码:

class Time
{
public:
	Time(int hour = 0, int minute = 0, int second = 0)
		: _hour(hour)
		, _minute(minute)
		, _second(second)
	{}

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

注意:

  1. 每个成员变量在初始化列表中只能出现一次。

  2. 类中包含以下成员,必须放在初始化列表位置进行初始化:
    引用成员变量
    const成员变量
    类类型成员(该类没有默认的构造函数)

  3. 尽量使用初始化列表初始化,因为不管你是否使用初始化列表,对于自定义类型成员变量,一定会先使用初始化列表初始化。

  4. 成员变量在类中声明次序就是其在初始化列表中的初始化顺序,与其在初始化列表中的先后次序无关。(最好初始化顺序与声明顺序相同

9 .explicit关键字
构造函数不仅可以构造与初始化对象,对于单个参数的构造函数,还具有类型转换的作用。

class Date
{
public:
	explicit Date(int year)
		: _year(year)
	{
		cout << "Date(int,int,int):" << this << endl;
	}

	Date& operator=(const Date& d) //赋值运算符重载 将后面进行详细说明
	{
		return *this;
	}

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


int main()
{
	Date d(2019);
	d = 2020;   // 2020---> 通过单参构造函数--->临时对象
	return 0;
}

用explicit修饰构造函数,将会禁止单参构造函数的隐式转换。

1.2 析构函数

  1. 概念:与构造函数功能相反,析构函数不是完成对象的销毁,局部对象销毁工作是由编译器完成的。而对象在销毁时会自动调用析构函数,完成类的一些资源清理工作
  2. 特性:
    1. 析构函数名是在类名前加上字符~。
    2. 无参数无返回值。
    3. 一个类有且只有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。
    4. 对象声明周期结束时,C++编译系统自动调用析构函数。
    5. 当类里面涉及资源管理时,用户必须显式定义析构函数,防止内存泄漏。
class SeqList
{
public:
	SeqList(int capacity = 10)
	{
		cout << "SeqList(int):" << this << endl;
		_array = (int*)malloc(capacity*sizeof(int));
		assert(_array);
		_capacity = capacity;
		_size = 0;
	}

	/*
	~SeqList()       //默认生成的析构函数
	{}
	*/

	~SeqList()
	{
		//cout << this << endl;
		if (_array)
		{
			free(_array);
			_capacity = 0;
			_size = 0;
		}
		cout << "~SeqList():" << this << endl;
	}


private:
	int* _array;
	int _capacity;
	int _size;
};

void TestSeqList()
{
	SeqList s;
}

int main()
{
	TestSeqList();
	return 0;
}
  1. 关于编译器自动生成的析构函数,是否会完成一些事情呢?下面的程序我们会看到,编译器生成的
    默认析构函数,对会自定类型成员调用它的析构函数。
class String
{
public:
	String(const char* str = "")
	{
		cout << "String(const char* ):" << this << endl;
		if (nullptr == str)
			str = "";

		_str = (char*)malloc(strlen(str)+1);
		strcpy(_str, str);
	}

	~String()
	{
		cout << "~String():" << this << endl;
		free(_str);
		_str = nullptr;
	}

private:
	char* _str;
};


class Person
{

private:
	String _name;
	String _gender;
	int _age;
};

void TestPerson()
{
	Person p;   // 
}

int main()
{
	TestPerson();
	return 0;
}

在Person类中:
默认的构造函数—将对象中_name和_gender两个String类型的对象构造好。
默认的析构函数—将_name和_gender两个String类的对象销毁掉

1.3 拷贝构造函数

  1. 概念: 只有单个形参,该形参是对本类类型对象的引用(一般常用const修饰),在用已存在的类类型对象创建新对象时由编译器自动调用。
  2. 特征:
    1. 拷贝构造函数是构造函数的一个重载形式。
    2. 拷贝构造函数的参数只有一个且必须使用引用传参,使用传值方式会引发无穷递归调用(调用构造函数)。
class Date
{
public:
	Date(int year = 1900, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
		cout << "Date(int,int,int):" << this << endl;
	}

	Date(const Date& d)
   {
 		_year = d._year;
 		_month = d._month;
 		_day = d._day;
 		cout << "Date(Date&):" << this << endl;
	}

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


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


void TestDate()
{
	Date d1(2019, 3, 22);
	Date d2(d1);
}

int main()
{
	TestDate();
	return 0;
}
  1. 若未显示定义,系统生成默认的拷贝构造函数。 默认的拷贝构造函数对象按内存存储按字节序完成拷贝,这种拷贝我们叫做浅拷贝,或者值拷贝。

当类中涉及资源管理时,浅拷贝就会引发问题

class String
{
public:
	String(const char* str = "")
	{
		cout << "String(const char* ):" << this << endl;
		if (nullptr == str)
			str = "";

		_str = (char*)malloc(strlen(str) + 1);
		strcpy(_str, str);
	}

	// 默认生成拷贝构造函数

	~String()
	{
		cout << "~String():" << this << endl;
		free(_str);
		_str = nullptr;
	}

private:
	char* _str;
};

void TestString()
{
	String s1("hello");
	String s2(s1); 
	
}

int main()
{
	TestString();
	system("pause");
	return 0;
}

运行结果:

在这里插入图片描述
程序出错的原因:
在程序结束时,要对构造的对象就行析构,因为s2是s1的浅拷贝,所有把s2析构之后,s1也被干掉了,但是系统不知道,还会去调用s1的析构函数,所以程序才会崩溃。

所以当类中涉及资源管理时,拷贝构造函数应该显式提供。

1.4 赋值操作符重载
1.41 运算符重载
C++为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数,也具有其返回值型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似。

函数名字为:关键字operator后面接需要重载的运算符符号
函数原型:返回值类型 operator操作符(参数列表)
注意:

  1. 不能通过连击其他符号来创建新的操作符。
  2. 重载操作符必须有一个类类型或者枚举类型的操作数。
  3. 用于内置类型的操作符,其含义最好不要改变。
  4. 作为类成员的重载函数时,其形参看起来比操作数数目少1,因为成员函数的操作符有一个默认的形参this,限定为第一个形参
  5. .* 、::、sizeof 、?:、. 以上5个运算符不能重载。

列举一些运算符重载的实现:

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

	Date(const Date& d)
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
		cout << "Date(Date&):" << this << endl;
	}


	// *this + day
	Date operator+(int day)
	{
		Date temp(*this);
		temp._day += day;
		return temp;
	}


	Date& DateAdd(int day)
	{
		_day += day;
		return *this;
	}
	//重载 ==
	bool operator==(const Date& d)
	{
		return _year == d._year &&
			_month == d._month &&
			_day == d._day;
	}
	//重载 !=
	bool operator!=(const Date& d)
	{
		return !(*this == d);
	}

	// 重载前置++
	Date& operator++()
	{
		_day += 1;
		return *this;
	}

	// 重载后置++
	Date operator++(int)
	{
		Date temp(*this);
		_day += 1;
		return temp;
	}
	//重载前置 --
	Date& operator--()
	{
		_day -= 1;
		return *this;
	}
	//重载后置--
	Date operator--(int)
	{
		Date temp(*this);
		_day -= 1;
		return temp;
	}

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

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


void TestDate()
{

	Date d1(2019, 3, 22);
	d1.DateAdd(3);
	d1 = d1 + 3;


	Date d2(d1);
	d2 = d1++;
	d2 = ++d1;

	Date d3(2018, 3, 22);
	d3 = d3;
	d3 = d1;
	//d3.operator=(d1);
	d1 = d2 = d3;
	//d1.operator=(d2.operator=(d3));
	if (d3 == d1);
	Date& d4 = d3;
	d4 = d3;
}

int main()
{
	TestDate();
	return 0;
}

1.42 赋值运算符重载

class Date
{ 
public :
	 Date(int year = 1900, 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;
	 }
 	}
private:
 	int _year ;
 	int _month ;
 	int _day ;
};

赋值运算符重载特点

  1. 参数类型。
  2. 返回值。
  3. 检测是否自己给自己赋值。
  4. 返回*this
  5. 一个类如果没有显式定义赋值运算符重载,编译器也会生成一个,完成对象按字节序的值拷贝。(浅拷贝

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

class Date
{
public:
	void TestFunc2()const
	{
		this->_day++;
		//_year++;
		//_month++;
	}
	void TestFunc2()
	{
		this->_day++;
		//_year++;
		//_month++;
	}
private:
	int _year;
	int _month;
	mutable int _day;
};

int main()
{
	Date d1;
	d1.TestFunc2();
	const Date d2;
	d2.TestFunc2();
	system("pause");
	return 0;
}

d1.TestFunc2(); 语句将会调用没有const修饰的TestFunc2函数;

d2.TestFunc2();语句将会调用有const修饰的TestFunc2函数;

1.6 取地址及const取地址操作符重载

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

class Date
{ 
public :
	 Date* operator&()
 {
	 return this ;
 }
 
 const Date* operator&()const
 {
	 return this ;
 }
private :
	 int _year ; // 年
	 int _month ; // 月
	 int _day ; // 日
};

1.7 static成员
概念: 声明为static的类成员称为类的静态成员,用static修饰的成员变量,称之为静态成员变量;用static修饰的成员函数,称之为静态成员函数。静态的成员变量一定要在类外进行初始化。

特性:

  1. 静态成员为所有类对象共享,不属于某个具体实例。
  2. 静态成员变量必须在类外进行定义,定义时不添加static关键字。
  3. 类静态成员即可用类名::静态成员或者对象.静态成员来访问
  4. 静态成员函数没有隐藏的this指针,不能访问任何非静态成员
  5. 静态成员和类的普通成员一样,也有public、protected、private3种访问级别,也可以具有返回值。

面试题: 实现一个类,计算程序中创建出了多少类对象。

class A
{
public:
	A()
	{
		++_count;
	}
	A(const A& t)
	{
		++_count;
	}

	static int GetCount()
	{
		return _count;
	}
private:
	static int _count;
};
int A::_count = 0;

void Test()
{
	cout << A::GetCount() << endl;// 0
	A a1, a2;
	A a3(a1);
	cout << A::GetCount() << endl;//3
}
int main()
{
	Test();
	system("pause");
	return 0;
}

输出结果: 0 和 3;

1.8 友元
1.81 友元函数
问题:现在我们尝试去重载operator<<,然后发现我们没办法将operator<<重载成成员函数。因为cout的输出流对象和隐含的this指针在抢占第一个参数的位置。this指针默认是第一个参数也就是左操作数了。但是实际使用中cout需要是第一个形参对象,才能正常使用。所以我们要将operator<<重载成全局函数。但是这样的话,又会导致类外没办法访问成员,那么这里就需要友元来解决。operator>>同理。

重载 operator<<
条件:

  1. 两个参数:参数一 一定为ostream&, 参数2输出的内容
  2. 必须要有返回值:ostream&, 支持连续输出
  3. 少做格式化操作:比如换行
  4. 将该函数作为类的友元函数
class Date
{
	// 友元函数
	friend ostream& operator<<(ostream& _cout, const Date& d);
public:
	Date(int year = 1900, int month = 1, int day = 1)
		: _year(year)
		, _month(month)
		, _day(day)
	{
		cout << "Date(int,int,int):" << this << endl;
	}

	void PrintDate()
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}

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

ostream& operator<<(ostream& _cout, const Date& d)
{
	// _cout<<d.GetYear()<<"-"<<d.GetMonth()<<"-"<<d.GetDay();
	_cout << d._year << "-" << d._month << "-" << d._day;
	return _cout;
}


int main()
{
	Date d(2019, 3, 24);
	d.PrintDate();
	cout << 10 << endl; 
	// cout<<10  cout<<endl;

	cout << d << endl;
	//cout << 10;
	//cout << d;

	//d.operator<<(cout);
	//d << cout;
	system("pause");
	return 0;
}

运行结果:在这里插入图片描述
友元函数可以直接访问类的私有成员,它是定义在类外部的普通函数,bu不属于任何类,但需要在类的内部声明,声明是需要加friend关键字
注意:

  1. 友元函数可以访问类的私有成员,但不是类的成员函数。
  2. 友元函数不能用const修饰。
  3. 友元函数可以在类定义的任何地方声明,不受类访问限定符限制。
  4. 一个函数可以是多个类的友元函数。
  5. 友元函数的调用和普通函数的调用原理相同。

1.82 友元类

class Date
{
	friend class Time;//声明时间类为日期类的友元类,则在时间类中就可以直接访问日期类中的私有成员变量
public:
	Date(int year = 1900, int month = 1, int day = 1)
		: _year(year)
		, _month(month)
		, _day(day)
	{
		cout << "Date(int,int,int):" << this << endl;
	}

private:
	int _year;
	int _month;
	int _day;
};
class Time
{

public:
	Time(int hour = 0, int minute = 0, int second = 0)
		: _hour(hour)
		, _minute(minute)
		, _second(second)
	{
		cout << this->_hour << endl;
	}

	void TestFriendClass()
	{
		Date d;
	}

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

void Test()
{
	Time t;
	t.TestFriendClass();

}
int main()
{
	Test();
	system("pause");
	return 0;
}

声明时间类为日期类的友元类,则在时间类中就可以直接访问日期类中的私有成员变量

注意:

  1. 友元关系是单向的,不具有交换性。
  2. 友元关系不能传递
    即:如果B是A的友元,C是B的友元,不能说明C是A的友元。

1.9 内部类
概念: 如果一个类定义在另一个类的内部,这个内部类就叫做内部类。注意此时这个内部类是一个独立的类,它不属于外部类,更不能通过外部类的对象去调用内部类。外部类对部类没有任何优越的访问权限。
注意: 内部类就是外部类的友元类。注意友元类的定义,内部类可以通过外部类的对象参数来访问外部类中的
所有成员。但是外部类不是内部类的友元。

特性:

  1. 内部类可以定义在外部类的任意位置。
  2. 注意内部类可以直接访问外部类中的static、枚举成员,不需要外部类的对象或类名。
  3. sizeof(外部类) = 外部类,和内部类没有任何关系。

谢谢大家的阅读!!

  • 1
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值