C++类与对象(初始化列表,explicit关键字,static成员,缺省声明,友元,内部类)

一、初始化列表

构造函数除了以普通的函数方式存在之外,也可以以初始化列表的形式存在。
初始化列表:以一个冒号开始,接着是一个以逗号分隔的数据成员列表,每个成员变量后面跟一个放在括号中的初始值或表达式。

1.与函数形式的构造函数对比

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

这是之前实现的一种构造函数。

Data(int year, int month, int day)
		:_year(year)
		,_month(month)
		,_day(day)
	{
    
    }

这是初始化类表版本的构造函数。
注意:初始化列表是成员变量定义的地方,相当于全部在定义的时候就已经初始化了。

2.初始化列表的优势

在定义构造函数时,我们建议使用初始化列表形式来定义构造函数,因为在一些特殊情况下,函数形式是无法进行初始化的。

class Data
{
    
    
private:
		int _year;
		int _month;
		int _day;
		const int _n;
public:
	Data(int year, int month, int day,const int n)
	{
    
    
		_year = year;
		_month = month;
		_day = day;
		_n = n;
	}
};
int main()
{
    
    
	Data d1(2022, 1, 1,6);
}

当成员变量具有常属性的时候,不能像如上进行初始化,因为常属性的变量必须在定义的时候被初始化。私有类型中只是对其进行的一个声明,这时候我们发现就没有机会对其进行初始化了。
而在初始化列表就是成员变量被定义的地方,所以使用初始化列表版本的构造函数就可以对常属性的变量进行初始化了。

class Data
{
    
    
private:
		int _year;
		int _month;
		int _day;
		const int _n;
public:
	Data(int year, int month, int day)
		:_year(year)
		,_month(month)
		,_day(day)
		,_n(1)
	{
    
    }
};
int main()
{
    
    
	Data d1(2022, 1, 1);
}

初始化列表在定义的时候就可以直接给常属性变量进行赋值,因为初始化列表是成员变量定义的地方,常属性变量只能在其被定义的地方初始化。

3.两种定义方式的本质区别

d1中的成员变量都是在定义类的时候定义完了,但是第一种方式相当于只是定义了成员变量,需要调用函数才能对成员变量进行初始化。而第二种方式相当于在定义类的时候就已经将它们初始化了。如果只想在定义类的时候对常属性的变量进行初始化,我们还可以这样写:

Data(int year, int month, int day)
		:_n(10)
	{
    
    
		_year = year;
		_month = month;
		_day = day;
	}

所以说初始化列表其实是包含了第一种定义构造函数的情况的。
</font color=red>其实成员变量的定义都发生在初始化列表中,只不过第一种没写也就没进行初始化而已

4.三种使用初始化列表的情况

1.当有常属性成员变量时。
2.当有引用定义的成员时。
3.当有没有默认构造函数的自定义类型成员变量时。

class A
{
    
    
private: int _b;
public:A(int a)
{
    
    
	_b = a;
}
};
class Data
{
    
    
private:
		const int _n;
		int& _ret;
		A _a;
public:
	Data(int a)
		:_n(10)
		,_ret(a)
		,_a(10)
	{
    
    }
};
int main()
{
    
    
	int i = 0;
	Data d1(i);
}

这三种情况使用初始化列表初始化如上,但是要注意,_ret并不是i的引用而是a的引用,改变i的值无法改变_ret的值,如果想改变应该使用引用传参。

5.初始化顺序

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

class A
{
    
    
private: int _a2;
	     int _a1;
public:A(int a)
	:_a1(1)
	,_a2(_a1)
{
    
    }
};
int main()
{
    
    
	A a(1);
}

在这里插入图片描述

比如上面这段代码,执行之后_a1被赋值成了1,但是_a2却被赋值成了随机值。
这是因为_a2的声明比_a1早,所以先初始化的是_a2,然后初始化的_a1。

二、explicit关键字

1.对象的隐式类型转换

在定义类的时候也会发生强制类型的转换。

class Data
{
    
    
private:
	int _year;
	/*int _month;
	int _day;*/
public:
	Data(int year)
		:_year(year)
	/*	,_month(month)
		,_day(day)*/
	{
    
    }
};
int main()
{
    
    
	Data d1(2022);
	d1 = 10;
}

</font color=blue>这里是一个隐式类型转换,这里的意思就是首先用10构造一个临时对象,再用这个对象拷贝构造d1。
虽然一共发生了两次拷贝构造,但是只调用了一次拷贝构造函数,这是编译器为了方便而做的优化。

我们发现这里一共调用了两次函数,一个是构造函数,一个是拷贝构造,编译器优化的时候把拷贝构造给优化掉了。

2.explicit

expilcit可以阻止对象的隐式类型转换的发生。

explicit Data(int year)
		:_year(year)
	{
    
    }

这样当再将d1=20时,编译器就会发生报错。

三、static成员

1.概念

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

2.特性

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

3.应用

最简单的一个应用,我们可以判断一个类构造了多少个对象。

class Data
{
    
    
private:
	int _year;
	int _month;
	int _day;
	static int count;
public:
	static int Getcount()
	{
    
    
		return count;
	}
	explicit Data(int year=1,int month=1,int day=1)
		:_year(year)
		,_month(month)
		,_day(day)
	{
    
    
		count++;
	}
};
int Data::count = 0;
int main()
{
    
       
	Data d1(2022,1,1);
	Data d2;
	Data d3;
	cout << d1.Getcount() << endl;
}

这里count是静态成员变量,Getcount是静态成员函数。
类中的count只是一个声明,count的定义要发生在类外。count不属于某一个对象,它属于全局范围某一个对象对count的改变是永久的改变。对于count有两种访问方式,可以直接在类域中访问,也可以在对象中访问。

Data::count=1;
d1.count=1;

两者效果是一样的。
在这里插入图片描述
同时静态成员函数由于没有this指针,所以不能访问成员变量。

static int Getcount()
	{
    
    
	_year=1;//错误,不能访问成员变量
		return count;
	}

静态成员函数也有两种调用方式:

d1.Getcount();
Data::Getcount()

</font color=red>只有静态成员函数或变量才可以使用Data::来进行调用,普通成员只能使用对象名.来进行调用。

四、缺省声明

为了规避编译器默认构造函数不能初始化内置类型,C++11引入了缺省声明。

class Data
{
    
    
private:
	int _year = 1;
	int _month = 2;
	int _day = 3;//缺省声明
public:
	void Print()
	{
    
    
		cout << _year << " " << _month << " " << _day << endl;
	}
};
int main()
{
    
      
	Data d1;
	d1.Print();
}

当构造函数不对成员变量进行赋值时,成员变量使用缺省值。
在这里插入图片描述
</font color=red>注意:静态的成员变量不能给缺省值,必须在类外面全局位置进行初始化

五、友元

1.友元函数

(1)例子

友元函数在上一节其实提到过,还是引用之前的例子:

class Data
{
    
    
	friend void operator<<(ostream& out, const Data& d);
private:
	int _year;
	int _month;
	int _day;
public:
	Data(int year = 0, int month = 1, int day = 1)
	{
    
    
		_year = year;
		_month = month;
		_day = day;
	}
};
void operator<<(ostream& out, const Data& d)
{
    
    
	out << d._year<<" " << d._month << " " << d._day;
}
int main()
{
    
    
	Data d1(2022, 1, 3);
	Data d2(2022, 3, 15);
	cout << d1;
}

这里在操作符重载中,为了使函数operator<<可以调用类中的成员_year等,使用了友元函数,即在类中加一行声明:

friend void operator<<(ostream& out, const Data& d);

这样这个函数就可以自由使用该类中的私有成员变量了。

(2)说明

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

2.友元类

友元类的所有成员函数都可以是另一个类的友元,都可以访问类中的非公有成员。

1.友元关系是单向的,不具有交换性。
2.友元关系不能传递。

class A
{
    
    
	friend class B;
private:
	int _a;
	B b;
public:
	void fun1()
	{
    
    
		b._b = 1;//会报错,无法访问到B中的私有成员
	}
};
class B
{
    
    
private:
	int _b;
	A a;
public:
	void fun2()
	{
    
    
		a._a = 1;
	}
};

</font color=red>在A类中声明友元类B,表示的是在B中的成员函数可以访问到A中的私有成员,但是A中的成员函数不能访问到B中的私有成员

六、内部类

1.概念

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

2.特性

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

class A
{
    
    
private:
	static int k;
	int h;
public:
	class B
	{
    
    
		void f(const A& a)
		{
    
    
			cout << k << endl;
			cout << a.h << endl;
		}
		void Print()
		{
    
    
			cout << 1 << endl;
		}
	};
};
int A::k = 1;
int main()
{
    
    
	A::B b;
	return 0;
}

此时B就是A的一个内部类,B中可以直接访问A中的静态成员,也可以访问A中的私有变量。
</font color=red>注意:在使用内部类B定义对象时,需要加上在A域中,A::B b,且此时b不能访问B中的成员函数。

七、总结

至此,类与对象的总结到此结束,类与对象是面向对象编程中的重要的部分,C++通过类,将一个对象的属性和行为结合在一起。使其更符合人们对于一件事物的认知,将属于该对象的所有东西打包在一起,通过访问限定符选择性的将其部分功能开放出来与其他对象进行交互,而对于对象内部的一些实现细节,外部用户不需要知道,知道了有些情况也没用,反而增加了使用或者维护的难度,让整个事情复杂化。

猜你喜欢

转载自blog.csdn.net/qq_51492202/article/details/122656438