之前,我们谈论了关于类的基本概念,了解了类和对象的基本使用以及关于类的六个默认构造函数等。那么今天就来说一说在C++类中你可能还不太了解的黑暗料理~~ 学会了之后,你会发现,原来还可以这么玩。。
上面我写了一个简单的日期类函数,虽然调用构造函数之后,对象中已经有了一个初值,但不能称为类对象成员的初始化,构造函数体内的语句只能称之为赋初值,而不是初始化。因为初始化只能初始化一次,但是构造函数体内可以被多次赋值。 那么类对象成员到底是怎样进行初始化的呢?
初始化列表 在C++中,引入了初始化列表这个概念,专门用来进行对类对象成员函数初始化,以一个冒号开始,接着是一个以逗号分隔的数据成员列表,每个"成员变量"后面跟一个放在括号中的初始值或表达式。
class Date{ public: Date(int year,int month,int day) : _year(year) , _month(month) , _day(day) {} private: int _year; //声明 int _month; int _day; }注:
每个成员变量只能在初始化列表中出现一次,(初始化只能被初始化一次)。类中包含const成员变量,引用成员变量,类类型成员变量(该类没有默认的构造函数)必须在初始化列表中初始化。这里的问题就是,这三类成员必须在定义的同时进行初始化。成员变量在类中的声明次序就是成员变量在初始化列表中的初始化顺序,与列表中的先后顺序无关。explicit关键字
对于explicit关键字,我相信大多初学者跟我一样,也不知道它是用来做什么的,那是我们自己把构造函数想的过于简单了,其实构造函数不仅仅可以用来初始化,对于单个参数或者n个参数,但是n-1个参数提供了默认值这样的构造函数还具有类型转换的功能。
class A{ public: A(){}; A(int a); A(int a,int b); A(int a,int b = 10); ~A(); private: int _a; int _b; }无参构造函数因为没有参数,故不能进行类型转换,单个参数的构造函数可以进行类型转换,例如 A aa; aa = 12.2; 这里就会将12.2 转换为A类型。同理第三个构造函数不行,第四个可以。我们再来看看加上explicit关键字。
class A{ public: A(){}; explicit A(int a); A(int a,int b); explicit A(int a,int b = 10); ~A(); private: int _a; int _b; }加上explicit关键字之后,就不允许单参构造函数进行隐式类型转换。
static成员
概念:声明为static成员变量称为静态成员变量,声明为static成员函数称为静态成员函数。静态成员变量要在类外初始化。特性: 静态成员为所有对象共享,不属于某个实例 静态成员变量必须在类外进行初始化,不能加static关键字。 静态成员可以用类::静态成员 或者 对象.静态成员访问。 静态成员没有this指针,所以它不能访问非静态成员。 静态成员函数与普通成员函数一样,也具有三种访问权限。 不能将静态成员函数定义为虚函数。 class A{ public: A(); void print(); static int show(); private: int _a; static int _b; } //对静态成员变量要在类外进行初始化 int A::a = 10; void Test(){ A::show(); //访问形式 A aa; aa._b = 20; }static关键字的用法总结
static关键字在C/C++中都可以用来修饰内置类型变量以及函数。
在C语言中
static修饰局部变量:static修饰的局部变量将会改变它的生命周期,将其从当前代码块改变为整个程序。static修饰全局变量以及全局函数:static修饰全局变量以及全局函数将会改变它的链接属性,改变了它的作用域,将其改变为当前文件,这就起到了一个隐藏作用,因为未加static关键字的全局变量和函数都具有全局可见性,其它源程序也能访问到,有可能就产生了命名冲突等问题。 应用全局变量需要用extern 进行声明。static默认初始化为0,其实全局变量也具备这一属性,因为全局变量也存储在静态存储区,在静态存储区当中,内存中所有字节默认值都是0x00。在C++中
static关键字通常用来声明类成员函数以及成员变量。声明为静态成员之后,该成员为整个类所共享,不属于某个对象。使用细节如上面static的特性一样。友元函数
概念:友元分为友元类以及友元函数,友元提供了一种突破封装的方式,提供了便利,但同时它也会增加耦合度,破坏封装,所以友元不易多用。为什么要使用友元? 如果我们尝试去重载operator<<,然后发现我们没办法将operator<<重载成成员函数。因为cout的输出流对象和隐含的this指针在抢占第一个参数的位置。this指针默认是第一个参数也就是左操作数了。但是实际使用中cout需要是第一个形参对象,才能正常使用。所以我们要将operator<<重载成全局函数。但是这样的话,又会导致类外没办法访问成员,那么这里就需要友元来解决。operator>>同理。 class Date{ friend ostream& operator<<(ostream& _cout, const Date& d); friend istream& operator>>(istream& _cin, Date& d); public: Date(int year,int month,int day) : _year(year) , _month(month) , _day(day) {} //重载输出 ostream& operator<<(ostream& _cout,const Date& d){ _cout<<_year<<"-"<<_month<<"-"<<_day<<endl; return _cout; } //重载输入 istream& operator>>(istream& _cin,Date& d){ _cin>>_year; _cin>>_month; _cin>>_day; return _cin; } private: int _year; //声明 int _month; int _day; }注:
友元函数可访问类的私有成员,但不是类的成员函数友元函数不能用const修饰友元函数可以在类定义的任何地方声明,不受类访问限定符限制一个函数可以是多个类的友元函数友元函数的调用与普通函数的调用和原理相同友元类
友元类的所有成员函数都可以是另一个类的友元函数,都可以访问另一个类中的非公有成员。友元关系是单向的,不具有交换性。如果B是A的友元,C是B的友元,则不能说明C时A的友元。 class Date; // 前置声明 class Time { friend class Date; // 声明日期类为时间类的友元类 public: Time(int hour, int minute, int second) : _hour(hour) , _minute(minute) , _second(second) {} private: int _hour; int _minute; int _second; }; class Date { public: Date(int year = 1900, int month = 1, int day = 1) : _year(year) , _month(month) , _day(day) {} void SetTimeOfDate(int hour, int minute, int second) { // 直接访问时间类私有的成员变量 _t._hour = hour; _t._minute = minute; _t.second = second; } private: int _year; int _month; int _day; Time _t; };内部类
内部类就是定义在一个类的内部,这个类是一个独立的类,它不属于外部类。注:内部类是外部类的友元,但外部类不是内部类的友元。内部类可以定义在外部类的public、protected、private都是可以的。注意内部类可以直接访问外部类中的static、枚举成员,不需要外部类的对象/类名。sizeof(外部类)=外部类,和内部类没有任何关系。