四、类和对象(下)

1. 再谈构造函数

在创建对象时,编译器会调用构造函数,给对象中的成员变量初始值。

class Date
{
public:
	//构造函数
	Date(int year = 1, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
private:
	int _year;
	int _month;
	int _day;
};

虽然上述构造函数调用后,对象中已经有了初始值,但这并不能将其称作类对象成员的初始化,,构造函数中的语句只能称为赋值。因为初始化只能初始化一次,但构造函数内可以多次赋值。

class Date
{
public:
	//构造函数
	Date(int year = 1, int month = 1, int day = 1)
	{
		_year = year;
		_year = 2024;
		_month = month;
		_day = day;
	}
private:
	int _year;
	int _month;
	int _day;
};

构造函数初始化还有另一种方式,就是初始化列表。

1.1 初始化列表

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

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

注意事项:

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

2. 对于引用成员变量,const成员变量以及没有默认构造的类类型变量,必须在初始化列表中进行初始化。 

class Time
{
public:
	Time(int hour)
		:_hour(hour)
	{}
private:
	int _hour;
};

class Date
{
public:
	Date(int& x, int year = 1, int month = 1, int day = 1)
		:_year(year)
		, _month(month)
		, _day(day)
		,_t(12)
		,ret(x)
		,_n(1)
	{}

	void Print() const
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}
private:
	int _year;
	int _month;
	int _day;

	Time _t;		//没有默认构造
	int& ret;		//引用
	const int _n;	//const
};

注意:默认构造函数是指不用传参就可以调用的构造函数,主要包括:1. 编译器自动生成的构造函数 2. 无参的构造函数 3. 全缺省的构造函数

class A
{
public:
	//这个不叫默认构造函数
	A(int val)
	{
		_val = val;
	}
private:
	int _val;
};

class B
{
public:
	B()
		:_a(2024)	//必须用初始化列表进行初始化
	{}
private:
	A _a;	//自定义成员,A无默认构造函数
};

 3. C++11支持在成员变量声明的位置给缺省值,这个缺省值主要是给没有显示在初始化列表初始化的成员使用的,同时尽量用初始化列表初始化。

class Time
{
public:
	Time(int hour)
		:_hour(hour)
	{}
private:
	int _hour;
};

class Date
{
public:
	Date()
		:_month(7)
	{}

	void Print() const
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}
private:
	//声明 缺省值->初始化列表
	int _year = 2024;
	int _month = 7;
	int _day;

	Time _t = 1;		
	int* ptr = (int*)malloc(12);
	const int _n = 1;	
};

int main()
{
	Date d1;
	d1.Print();
	return 0;
}

在初始化时,每个成员都要走初始化列表,分为两种情况:一是在初始化列表中的成员,则直接进行初始化;二是不在初始化列表中的成员,如果有缺省值,直接用缺省值初始化;如果没有缺省值,如果是内置类型,要看编译器是否初始化,这个不确定;如果是自定义类型,会调用默认构造,如果没有,就会编译报错。 

4. 初始化列表按照成员变量在类中的声明顺序进行初始化,建议声明顺序和初始化顺序一样。

class A
{
public:
	A(int a)
		:_a1(a)
		, _a2(_a1)
	{}
	void Print() {
		cout << _a1 << " " << _a2 << endl;
	}
private:
	int _a2 = 2;
	int _a1 = 2;
};
int main()
{
	A aa(1);
	aa.Print();
}

看到上面的代码,可以猜一下,输出结果是什么?

最后结果是1和随机值。

因为初始化列表按照成员变量在类中的声明顺序进行初始化,可以看到,_a2先声明,_a1后声明,所以_a2先进行初始化,因为此时_a1是随机值,所以_a2是随机值,之后用1初始化_a1。 

通过调试我们可以更直观的看到这个过程。

 

2. 类型转换

class A
{
public:
	A(int a)
		:_a1(a)
	{}
void Print() {
	cout << _a1 << " " << _a2 << endl;
	}
private:
	int _a1 = 2;
	int _a2 = 2;
};
int main()
{
	A aa1(1);
	aa1.Print();

	//隐式类型转换
	A aa2 = 2;
	//A tmp(2);
	//A aa2(tmp);
	aa2.Print();
	return 0;
}

在语法中,代码中A aa2 = 2等价于下面两句代码:

	A tmp(2);
	A aa2(tmp);

对于隐式类型转换,编译器会先用2构造一个A的临时对象,再用这个对象拷贝构造aa2. 编译器在遇到连续构造+拷贝构造时会优化成直接构造。 

 其实,隐式类型转换我们很早就遇到过。

int a = 2;
double b = a;

  C++11之后支持多参数传参。

class A
{
public:
	A(int a)
		:_a1(a)
	{}

	A(int a1,int a2)
		:_a1(a1)
		,_a2(a2)
	{}

	void Print()
	{
		cout << _a1 << " " << _a2 << endl;
	}
private:
	int _a1 = 2;
	int _a2 = 2;
};
int main()
{
	A aa1(1);
	aa1.Print();

	//隐式类型转换
	A aa2 = 2;
	//A tmp(2);
	//A aa2(tmp);
	aa2.Print();

	A aa3 = { 5.6 };
	aa3.Print();
	return 0;
}

 若我们想禁止单参数构造函数的类型准换,可以用关键字explicit来修饰构造函数。

	explicit A(int a)
		:_a1(a)
	{}

3. static成员

 用static修饰的成员变量,称为静态成员变量。用static修饰的成员函数,称为静态成员函数。静态成员变量必须在类外进行初始化。

特性:

1. 静态成员变量为所有类成员对象共享,不属于某个具体的对象,不存放在对象中,存放在静态区。

class A
{
public:
	A()
	{
		++_scount;
	}
private:
	//类里面声明
	static int _scount;
};

//类外面初始化
int A::_scount = 0;

int main()
{
	A a1;
	cout << sizeof(a1) << endl;
	return 0;
}

计算类的大小或类类型对象的大小时,静态成员不计入其总大小之和中。

 2. 静态成员也是类的成员,受访问限定符的限制。但突破类域就可以访问静态成员。

class A
{
public:
private:
	static int _n;
};

int A::_n = 0;

3. 静态成员函数没有this指针,可以访问静态成员,不能访问非静态成员。 

class A
{
public:
	//默认构造
	A()
	{
		++_scount;
	}

	//拷贝构造
	A(const A& a)
	{
		++_scount;
	}

	//析构
	~A()
	{
		--_scount;
	}

	static int GetACount()
	{
		_a++;
		return _scount;
	}
private:
	//类里面声明
	static int _scount;
	int _a = 1;
};

无法访问_a.

//计算程序中创建出了多少个类对象
class A
{
public:
	//默认构造
	A()
	{
		++_scount;
	}

	//拷贝构造
	A(const A& a)
	{
		++_scount;
	}

	//析构
	~A()
	{
		--_scount;
	}

	static int GetACount()
	{
		//_a++;
		return _scount;
	}
private:
	//类里面声明
	static int _scount;
	//int _a = 1;
};

//类外面初始化
int A::_scount = 0;

int main()
{
	cout << A::GetACount() << endl;
	A a1, a2;
	A a3(a1);
	cout << A::GetACount() << endl;
	return 0;
}

这里有一道题可以练手:求1+2+3+...+n_牛客题霸_牛客网 (nowcoder.com) 

4.  友元

友元提供了一种突破类访问限定符封装的方式,主要分为友元函数和友元类。虽然有时提供了便利,但友元会增加耦合度,破坏封装,不宜多用。

class A
{
private:
	int _a1 = 1;
	int _a2 = 2;
};

class B
{
private:
	int _b1 = 3;
	int _b2 = 4;
};

void fun(const A& a, const B& b)
{
	cout << a._a1 << endl;
	cout << a._a2 << endl;
	cout << b._b1 << endl;
	cout << b._b2 << endl;
}

int main()
{
	A aa;
	B bb;
	fun(aa, bb);
	return 0;
}

当我们写了这样一段代码,会发现编译错误,无法访问私有成员变量,为了解决这个问题,我们需要加上友元声明。

// 前置声明,否则A的友元函数声明编译器不认识B
class B;
class A
{
	//友元声明
	friend void fun(const A& a, const B& b);
private:
	int _a1 = 1;
	int _a2 = 2;
};

class B
{
	//友元声明
	friend void fun(const A& a, const B& b);
private:
	int _b1 = 3;
	int _b2 = 4;
};

void fun(const A& a, const B& b)
{
	cout << a._a1 << endl;
	cout << a._a2 << endl;
	cout << b._b1 << endl;
	cout << b._b2 << endl;
}

int main()
{
	A aa;
	B bb;
	fun(aa, bb);
	return 0;
}

上述代码还可以将其中一个类声明为另一个类的友元类,这样就可以访问另一个类的私有和保护成员。

class A
{
	//友元声明
	friend class B;
private:
	int _a1 = 1;
	int _a2 = 2;
};

class B
{
public:
	void fun1(const A& a)
	{
		cout << a._a1 << endl;
		cout << _b1 << endl;
	}

	void fun2(const A& a)
	{
		cout << a._a2 << endl;
		cout << _b2 << endl;
	}

private:
	int _b1 = 3;
	int _b2 = 4;
};

int main()
{
	A aa;
	B bb;
	bb.fun1(aa);
	bb.fun2(aa);
	return 0;
}

注意:

1. 友元类中的关系是单向的,比如A是B的友元,但B不是A的友元。

2. 友元类关系不能传递,比如A是B的友元,B是C的友元,但A不是C的友元。 

5. 内部类

如果一个类定义在另一个类的内部,那么这个类就是内部类。内部类是一个独立的类,跟定于在全局相比,他只受外部类类域限制和访问限定符限制,所以外部类定义的对象中不包含内部类。

内部类默认是外部类的友元类。 

class A
{
public:
	//B默认是A的友元
	class B
	{
	public:
		void fun(const A& a)
		{
			cout << _k << endl;
			cout << a._h << endl;
		}
	};
private:
	static int _k;
	int _h = 1;
};

int A::_k = 2;

int main()
{
	cout << sizeof(A) << endl;

	A::B b;
	A aa;

	b.fun(aa);
	return 0;
}

6. 匿名对象

用类型(实参)定义出来的对象叫匿名对象。

匿名对象生命周期只在当前一行。

class A
{
public:
	A(int a = 0)
		:_a(a)
	{}

	~A()
	{
		cout << "~A()" << endl;
	}
private:
	int _a;
};

class Solution
{
public:
	int Sum_Solution(int n) 
	{
		return n;
	}
};

int main()
{
	//匿名对象
	A();
	A(1);

	cout << Solution().Sum_Solution(10) << endl;
	return 0;
}

  • 14
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值