类与对象(下)

目录

一.构造函数的补充

初始化列表的注意事项: 

总结:

二.类型转换

 三.Static成员

四.友元(friend) 

五.内部类

六.匿名对象


 

一.构造函数的补充

在上一篇文章类与对象(中)中我们就初次认识了构造函数,但是那篇文章中我们只学了百分之七十左右的内容,在这边文章中我们将对它进行完善。在那篇文章中是不是没看懂为什么对于自定义类型(类,结构体,Union等关键字自己定义的类型),如果这个成员没有默认构造函数,那么就会报错,我们要初始化这个成员变量,需要用初始化列表才能解决。那么初始化列表是什么呢?

之前学习构造函数的时候,初始化成员都是在函数体里赋值的,但他还有另一种方式——利用初始化列表。初始化列表的使用形式是以一个冒号开始,接着是以一个逗号分隔的数据成员列表,每个“成员变量”后面都跟一个放在括号里面的初始值或表达式。

初始化列表的注意事项: 

1.每个成员变量在初始化列表只能出现一次,初始化列表也可以认为是每个成员变量定义初始化的地方。

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

	}
//也可为
  /*Data()
		:_year(1)
		,_month(1)
		,_day(1)
	{

	}*/
	void Print()
	{
		cout << _year << "/" << _month << "/" << _day << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};
int main()
{
	Data d1;
	d1.Print();
	return 0;
}

2.引用成员变量、const成员变量、没有默认构造的类类型变量,必须放在初始化列表位置进行初始化,否则编译器会报错。

class Time
{
public:
	Time(int hour)
		:_hour(hour)
	{
//如果调用了Time的实例化对象的默认默认构造函数将会打印Time()
		cout << "Time()" << endl;
	}
private:
	int _hour;

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

	}
	void Print()
	{
		cout << _year << "/" << _month << "/" << _day << endl;
	}
private:
	int _year;
	int _month;
	int _day;
	Time _t;//无默认构造函数(不需要传参的)
	int& _ref;//引用必须初始化
	const int _n;//const把变量变成常变量
};
//若不在初始化列表初始化
//错误	C2512	“Time” : 没有合适的默认构造函数可用
//错误	C2530	“Data::_ref”: 必须初始化引用
//错误	C2789	“Data::_n”: 必须初始化常量限定类型的对象
int main()
{
	int i = 0;
	Data d1(i);
	d1.Print();
	return 0;
}

3.C++11支持在成员变量声明给缺省值,主要是给没有显式在初始化列表初始化的成员用的。

猜猜下面下面代码中用构造函数1的结果是什么?用构造函数2的结果又是什么?


class Data
{
public:
   //1.
	Data()
	{

	}
  //2.
/*Data()
		:_year(2)
		,_month(3)
		,_day(4)
	{

	}*/
	void Print()
	{
		cout << _year << "/" << _month << "/" << _day << endl;
	}
private:
//成员声明
	int _year = 1;
	int _month = 2;
	int _day = 3;
};
int main()
{
	Data d1;
	d1.Print();
	return 0;
}

答案就是1:  1/2/3    2:   2/3/4

当成员在声明位置给了缺省值,但没有显式的在初始化列表初始化,那么初始化列表会用这个缺省值初始化,如果没有给缺省值,也没有显式的在初始化列表初始化,那么内置类型成员是否初始化取决于编译器,而自定义类型成员如果没有默认构造函数,编译会错误。

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还没初始化,_a1在vs中为随机值,_a1初始化为a,a=1,所以_a1初始化为1。

总结:

无论是否写初始化列表,每个构造函数都存在初始化列表。

无论是否在初始化列表显示初始化(还没开空间),每个成员变量都要走初始化列表。

二.类型转换

1.C++支持内置类型隐式类型转换为类类型对象,需要有相关内置类型为参数的构造函数。

2.构造函数前面加explicit就可以不再支持隐式类型转换。

​//首先我们先了解隐式类型转换
//short a='s';//'s'是char类型被转换成了short类型
//int j=0;
//double d=j;//j变量是int类型的转变成了double类型
class A
{
public:
	// 构造函数explicit就不再⽀持隐式类型转换
	// explicit A(int a1)
	A(int a1)
		:_a1(a1)
	{}
	//explicit A(int a1, int a2)
	A(int a1, int a2)
		:_a1(a1)
		, _a2(a2)
	{}
	void Print()
	{
		cout << _a1 << " " << _a2 << endl;
	}
		int Get() const
	{
		return _a1 + _a2;
	}
private:
	int _a1 = 1;
	int _a2 = 2;
};
​int main()
{
	// 1构造⼀个A的临时对象,再⽤这个临时对象拷⻉构造aa1
	// 编译器遇到连续构造+拷⻉构造->优化为直接构造
	A aa1 = 1;
	aa1.Print();
	const A& aa2 = 1;
	// C++11之后才⽀持多参数转化
	A aa3 = { 2,2 };
    return 0;
}

内置类型转变成类类型也是一样的,只是编译器会直接优化,就不再需要临时变量,也就没有不需要在调用拷贝构造了,直接进行构造函数。 

3.类类型的对象之间也可以隐式转换,需要相应的构造函数的支持。

class A
{
public:
	A(int a1)
		:_a1(a1)
	{}
	A(int a1, int a2)
		:_a1(a1)
		, _a2(a2)
	{}
	void Print()
	{
		cout << _a1 << " " << _a2 << endl;
	}
		int Get() const
	{
		return _a1 + _a2;
	}
private:
	int _a1 = 1;
	int _a2 = 2;
};
class B
{
public:
	B(const A& a)
		:_b(a.Get())
	{}
private:
	int _b = 0;
};
int main()
{
	A aa3 = { 2,2 };
	// aa3隐式类型转换为b对象
	// 原理跟上⾯类似
	B b = aa3;
	const B& rb = aa3;把A类类型转变成常引用
	return 0;
}

 三.Static成员

在C语言的时候我们知道static修饰的变量或函数会失去外部链接属性,它的作用在C++中也适用。

1.用static修饰的成员变量,称之为静态成员变量,静态成员变量一定要在类外进行初始化(类中的构造函数无法初始化静态变量)。即类里面声明,类外面初始化但要指定类域。

2.用static修饰的变量为所有类对象所共有,不属于某个具体的对象,不存在对象中,存放在静态区。⾮静态的成员函数,可以访问任意的静态成员变量和静态成员函数。

3.静态成员函数没有this指针

4.突破类域就可以访问静态成员,可以通过类名::静态成员或者对象.静态成员来访问静态成员变量和静态成员函数(初始化时)。

5.静态成员也受public、protected、private访问限定符的限制。

6.静态成员变量不能在声明位置给缺省值初始化,因为缺省值是个构造函数构造初始化列表的,静态成员不属于某个对象,所以不走初始化列表。

class C
{
public :
	C()
	{
		_a++;
	}
	C(const C& c)
	{
		_a++;
	}
	~C()
	{
		_a--;
	}
	static int get()//没有this指针
	{
		return _a;
	}
private:
//类内声明
	static int _a;//不能给缺省值,不走初始化列表
};
//类外初始化
int C::_a = 0;
int main()
{
	cout << C::get() << endl;
	C c1;
	C c2;
	C c3(c2);
	cout << C::get() << endl;//cout<<c3.get()<<endl;
}

猜猜输出了什么?

输出了0 3,这里也说明了staic修饰的变量只初始化一次

那static修饰的类什么时候调用析构和构造呢?

判断一下代码A,B,C,D类的实例化对象析构和构造顺序:

C c;
int main()
{
A a;
B b;
static D d;
return 0;
}

析构顺序:B     A      D     C(main函数中局部(先构造的后析构)先析构再析构static类(程序退出时才析构)再全局)

构造顺序:C     A     B      D  (main函数之前全局变量已经构造好了main函数里的变量就顺着构造)  

四.友元(friend) 

再上一篇文章类与对象(中)的输出(<<),输入(>>)重载时,它们不能放在类里面定义它的运算符重载的函数,而是要放在全局变量中去定义,当我们在全局定义的时候需要访问类中的成员变量,但是成员变量受类访问限定符限制,我们就用到了友元在类中声明函数。

但它还有很多注意事项:

1.友元是一种突破类访问限定符封装的方式,友元分为友元函数和友元类,在函数声明或者类声明的前面加friend,并且把友元声明放在一个类里面。

2.在外部定义的友元函数并不是类的成员函数。

3.友元函数可以在类中的任意地方声明,一般习惯在public之前,不受类访问限定符限制。

4.一个函数可以有多个类的友元函数。

class B;//重点,class A在classB之前,要先声明classBB
class A
{
	friend void func(const A& _a, const B& _b);
public:
	A()
		:a(3)
	{

	}
private:
	int a;
};
class B
{
	friend void func(const A& _a, const B& _b);
public:
	B()
		:b(2)
	{

	}
private:
	int b;
};
void func(const A& _a, const B& _b)//class A和class B的友元函数
{
	cout << _a.a << " " << _b.b;
}
int main()
{
	A a;
	B b;
	func(a,b);
	return 0;
}

5.友元类中的成员函数都可以是另一个类的友元函数,可以访问另一个类中的私有和保护成员。

class B;
class A
{
public:
	A()
		:a(3)
	{

	}
	void func(const B& b);//声明但func函数是A类的成员函数
private:
	int a;
};
class B
{
	friend void A::func(const B& _b);//友元函数声明
public:
	B()
		:b(2)
	{

	}
private:
	int b;
};  
void A::func(const B& _b)//A类中的成员函数是B类的友元函数
{
	cout << _b.b << endl;
}
int main()
{
	A a;
	B b;
	a.func(b);
	return 0;
}

6.友元类的关系是单向的,不具有交换性,即A类是B类的友元,但是B类不是A类的友元。也没有传递性,如果A是B的友元,B是C的友元,但不代表A是C的友元。

class B;
class A
{
public:
	A()
		:a(3)
	{

	}
	friend class B;
private:
	int a;
};
class B
{
public:
	int& func(A& _a)
	{
		return _a.a;
	}
	B()
		:b(2)
	{

	}
private:
	int b;
};  
int main()
{
	A a;
	B b;
	cout << b.func(a) << endl;
	return 0;
}

在A类中声明了B是A的友元,那么B类就可以访问A类中的私有和保护成员。 

五.内部类

1.一个类定义在另一个类的内部,这个内部的类就是内部类。内部类是一个独立的类,跟定义在全局的相比,它只是受外部类类域限制和访问限定符限制,所以外部类定义的对象中不包括内部类。即计算一个类的空间大小时内部类的空间不计算在内。

class A
{
public:
	A()
	{

	}
	class B
	{
	public:
		B()
		{

		}
	private:
		int b;
	};
private:
	int a;
};
int main()
{
	A a1;
	int ret = sizeof(a1);
	cout << ret << endl;
	return 0;
}

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

class A
{
public:
	A()
	:a(5)
	,c(2)
	{

	}
	class B
	{
	public:
		B(const A&a)//B可以访问A的私有的成员变量,B是A的友元
        :b(2)
		{
			b = a.a;
		}
		void print()
		{
			cout << b << endl;A
		}
	private:
		int b;
	};
private:
	int a=5;
	int c;
};
int main()
{
	A a1;
	A::B b1(a1);
	b1.print();
	return 0;
}

3.内部类本质上也是一种封装,当A类跟B类紧密关联时,A类实现出来主要就是给B使用的,那么可以考虑把A设计成B的内部类,如果放在private/protected位置,那么A类就是B类的专属内部类,其他地方都用不了。

求1+2+3+......+n

思路:实例化n个对象,调用构造函数的时候在构造函数中进行相加。

class Sum
{
public:
Sum()
{
    sum+=ref;
    ref++;
}
static int GetRet()
{
    return sum;
}
private:
static int sum;
static int ref;
};
int Sum::sum=0;
int Sum::ref=1;
class Solution {
public:
    int Sum_Solution(int n) {
        Sum arr[n];//变长数组在VS中不能用
        return Sum::GetRet();
    }
};

在这段代码中我们可以看到Sum类和Solution类紧密相关,所以我们可以把sum类设置为Solution类的内部类。

class Solution {
private://设计Sum为Solution的内部类,并把它放在Solution类的私有里,Sum类变成Solution类的专属类。
    class Sum
{
public:
Sum()
{
    sum+=ref;
    ref++;
}
static int GetRet()
{
    return sum;
}
private:
static int sum;
static int ref;
};
public:
    int Sum_Solution(int n) {
        Sum arr[n];
        return Sum::GetRet();
    }
};
int Solution::Sum::sum=0;
int Solution::Sum::ref=1;

六.匿名对象

 1.用类型(实参)定义出来的对象就是匿名对象(没有名字)。

2.匿名对象的生命周期只在当前一行,一般临时定义个对象当前用一下即可,就可以定义匿名对象。

class A
{
public:
	A(int a = 2)
		:a(3)
	{
		cout << "A()" << endl;
	}
	~A()
	{
		cout << "~A()" << endl;
	}
private:
	int a;
};
int main()
{
	//编译器不会报错,但是不建议这么用,编译器会分不清是函数的声明,还是aa1的初始化
	//A aa1();
	A();//匿名对象
	A(1);//匿名对象

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值