目录
前言
一、再谈构造函数
二、static成员
三、友元
四、内部类
五、匿名对象
总结:
前言
终于来到了我们类和对象的最后一部分,类与对象下。在这部分中,我们主要讲解类与对象的“插件” ,也就类似于拓展部分,比如友元函数,初始化列表等,让大家对C++类与对象有一个更深层次的理解。
一、再谈构造函数
在创建对象时,编译器通过调用构造函数,给对象中的每个成员一个合适的初始值。虽然调用了构造函数之后对象中已经有了一个初始值,但这并不能叫做对对象中的成员变量初始化,构造函数函数体内的部分只能称作赋初值。因为构造函数体内可以多次赋值,但初始化只能初始化一次。
在类中负责初始化工作的是初始化列表,是一个:以一个冒号开始,以逗号分隔的成员数据列表,每个成员变量后面跟着一个放在括号中的初始值或者表达式。
//以日期类为例
class Date
{
public:Date(int year, int month, int day)//以一个冒号开始,以逗号分隔的成员数据列表,每个成员变//量后面跟着一个放在括号中的初始值或者表达式。:_year(year),_month(month),_day(day){}
private:int _year;int _month;int _day;
};
每一个成员变量在初始化列表中只会出现一次(因为初始化只会进行一次),初始化列表就是成员变量初始化的地方。
注意,如果类中的成员变量包含如下类型,就必须把此变量放在初始化列表中进行初始化:
1、引用成员变量
2、const 成员变量
3、自定义类型成员(尤其是此类型没有默认构造函数时)
class A
{
public:A(int a):_a(a){}
private:int _a;
};class B
{
public:B(int a, int ref):_obj(a),_ref(ref), _n(10){}
private:A _obj;// 没有默认构造函数int& _ref; // 引用const int _n; // const
};
虽然没有强制要求其他类型也要写进初始化列表初始化,但在尽量用初始化列表,因为对于自定义类型来说,它一定会先使用初始化列表初始化。
另外,初始化列表中的初始化顺序与它在初始化列表中的先后顺序无关,而与它在类中声明的先后相关,越先声明,越先在初始化列表中初始化。
class A
{
public:A(int a1):_a1(a1), _a2(_a1){}void Print(){cout << _a1 << ' ' << _a2 << endl;}
private:int _a2;int _a1;
};int main()
{A a(1);a.Print();return 0;
}
从这段代码中可以看出,我们在类中先声明了_a2,随后声明了_a1,在初始化列表中,我们先初始化_a1,随后用_a1给_a2初始化。倘若初始化顺序是按照在初始化列表中的先后顺序,这段代码的结果就会是两个1,但最后却是:
这就可以说明,初始化列表中的初始化顺序与它在初始化列表中的先后顺序无关!
构造函数不仅可以构造与初始化对象,对于单个参数或者除第一个参数无默认值,其余均有默认值的构造函数来说,还会具有隐式类型转换的作用。
class Date
{
public:Date(int year, int month = 1, int day = 1):_year(year), _month(month), _day(day){}void Print(){cout << _year << ' ' << _month << ' ' << _day << endl;}
private:int _year;int _month;int _day;
};int main()
{Date a(1900);Date b = 2023;//1b.Print();a.Print();a = 2024;//2a.Print();return 0;
}
这里的1与2过程其实都是发生了隐式类型转换。实际上编译器背后会用2023,2024构造一个无名的对象,最后用无名对象给a与b赋值。
但倘若我们给构造函数前面加一个关键字explicit,就会编译报错,因为explicit修饰构造函数,禁止了单参构造函数类型转换的作用。
二、static成员
声明为static的类成员被称为类的静态成员,用 static修饰的成员变量,称之为:静态成员变量;用static修饰的成员函数,称之为静态成员函数。静态成员变量一定要在类外进行初始化。
class A
{
public:A() { ++_scount; }A(const A & t) { ++_scount; }~A() {--_scount; }static int GetACount() { return _scount; }
private:static int _scount;
};int A::_scount = 0;//在类外初始化void TestA()
{cout << A::GetACount() << endl;A a1, a2;cout << A::GetACount() << endl;A a3(a1);cout << A::GetACount() << endl;
}int main()
{TestA();cout << A::GetACount() << endl;return 0;
}
static修饰的类的静态成员有以下特性:
1、静态成员为所有此类的对象所共享,不属于某个具体的对象,并且存放在静态区。
2、静态成员变量必须在类外定义,定义时不添加static关键字,在类里只是声明。
3、类静态成员可以通过类名::静态成员或者对象.静态成员来访问。
4、静态成员函数没有隐藏的this指针,不能访问任何非静态成员。
5、静态成员也是类的成员,受到public,private,protected访问限制符的限制。
静态成员函数可以调用非静态成员函数,但是需要通过对象来调用非静态成员函数,因为非静态成员函数属于对象而不属于类。而非静态成员函数可以调用类的静态成员函数,因为静态成员函数属于类而不属于对象。
三、友元
友元提供了一种突破封装的方式,有时会为我们提供了便利。但是,友元会增加耦合度,破坏了封装,所以友元不宜多用。
友元分为友元函数与友元类。
1、友元函数
注意:
友元函数可以访问类的私有与保护成员,但不属于类的函数。
友元函数不能被const修饰(因为const不属于类成员函数,不受const限制)
友元函数可以在类定义的任何地方声明,不受访问限制符的限制(因为友元函数不属于类的成员函数)
一个函数可以是多个类的友元函数(我可以是很多人的朋友)
2、友元类
友元类的所有成员函数都是另一个类的友元函数,都可以访问另一个类的非公有成员。
注意:
1、友元关系是单向的,不具有交换性:
比如我们定义了一个A类与一个B类,在A类中我们声明B类为A类的友元类,那么我们就可以在B类中直接访问A类的非公有成员,但是A类不可以访问B类的非公有成员(我认为你是我的朋友,但别人不认为你是他的朋友)
2、友元关系不能传递:
倘若C是B的友元类,B是A的友元类,那么不能说明C是A的友元类.
3、友元关系不能继承
class Time
{friend class Date;//声明Date类是Time类的友元类,则可以直接在Date类里访问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;
};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){//Date类的成员函数,却可以直接访问Time类里的私有变量_t._hour = hour;_t._minute = minute;_t._second = second;}
private:int _year;int _month;int _day;Time _t;
};
四、内部类
概念倘若一个A类定义在一个B类的内部,那我们就称A为B的内部类。内部类是一个独立的类,它不属于外部类,更不能通过外部类的对象去访问内部类的成员。因为外部类对内部类没有任何优越的访问权限。恰恰相反,内部类天生是外部类的友元类,内部类可以访问外部类的非公有成员。
class Time
{class Date//天生是Time的友元类{public:Date(int year = 1900, int month = 1, int day = 1):_year(year), _month(month), _day(day){}void Couttime(const Time&a){//Date类的成员函数,却可以直接访问Time类里的私有变量cout << a._hour << ' ' << a._minute << ' ' << a._second;cout << k;}private:int _year;int _month;int _day;};
public:Time(int hour = 0, int minute = 0, int second = 0):_hour(hour), _minute(minute), _second(second){}
private:int _hour;int _minute;int _second;static int k;
};
五、匿名对象
class A
{
public:A(int a = 0):_a(a){cout << "A(int a)" << endl;}~A(){cout << "~A()" << endl;}
private:int _a;
};int main()
{A();cout << 111 << endl;return 0;
}
运行代码,我们可以看见:
通过A();我们生成了一个匿名的对象,这个匿名对象的生命周期只有这一行,过后就会自动调用析构函数。
总结:
通过理解类与对象的概念,我们可以更好地应用面向对象的设计思想来解决实际问题,提高代码的质量和效率。同时,也能够更好地理解和应用面向对象编程语言中的各种特性和技术,如继承、多态、封装等。
在学习过程中,我们可能会遇到各种挑战和困难,但只要保持学习的态度,不断探索和实践,就能够不断提升自己的编程水平。希望本文能够为您对类与对象的理解提供一些帮助,并在您的编程之旅中起到一定的指导作用。