🎈个人主页:🎈 :✨✨✨初阶牛✨✨✨
🐻推荐专栏1: 🍔🍟🌯C语言初阶
🐻推荐专栏2: 🍔🍟🌯C语言进阶
🔑个人信条: 🌵知行合一
🍉本篇简介:>:深入理解构造函数,介绍友元函数,内部类等等
金句分享:
✨努力不一定是为了钱,还有骨子里的自信与淡定✨
目录
- 一、构造函数的深入理解
- 1.1 初始化列表
- 1.2 关键字:`explicit`
- 二、`Static`成员变量/函数
- (1)定义
- (2)静态成员函数为什么一定要在类外面初始化:
- 总结:
- 三、 友元
- (1) 友元函数
- (2)友元类
- 四、内部类(天生友元)
一、构造函数的深入理解
1.1 初始化列表
前面,我们已经学习过构造函数,在创建对象的时候,编译器会自动调用构造函数,用于给初始化对对象的成员变量赋予初始值.那构造函数体内的语句时初始化吗?
class Date
{
public:
Date(int year, int month, int day)
{
//下面这些是对成员变量的初始化操作吗?
_year = year;
_month = month;
_day = day;
}
private:
int _year;
int _month;
int _day;
};
答案:
并不是初始化操作,因为初始化只能初始化一次,是指变量在创建的时候被赋予的初始值.而构造函数体内可以进行多次赋值.
那成员变量是在哪里初始化的呢?
运行结果:
2023-2-1
类的成员变量会先经过初始化列表,再走函数体内赋值,所以month
初始化为了1
,后又在函数体内被重新赋值.为了2
.
在构造函数的函数名参数后与{}中间的区域是成员变量初始化的地方.
初始化列表:
以一个冒号开始,接着是一个以逗号分隔的数据成员列表,每个"成员变量"后面跟一个放在括号中的初始值或表达式。(如上图)
(1) 初始化列表的作用:
我们未使用初始化列表之前,一直都是在函数体内赋值,那初始化列表有什么用呢?
试着看一下下面这段代码.
对于下列成员变量,只能使用初始化列表进行初始化,因为这些成员变量只能在定义时就给出初始化的值:
const
成员变量- 引用成员变量
- 没有默认构造函数的自定义类型成员
正确写法:
class Date
{
public:
Date(int year = 2023, int month = 1, int day = 1)
:_year(year)
, _month(month)
, _day(day)
, pa(day) //在初始化列表对这些特殊的成员变量初始化
, b(2)
,t1(6,15,20)
{
_month = 2;
}
void Print()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
int _year;
int _month;
int _day;
int& pa;
const int b;
Time t1;
};
(2) 初始化列表的初始化顺序与成员变量的声明有关,与写在初始化列表的顺序无关.
示例;
并不会先个c
赋值,而是按a,b,c
的顺序进行初始化,此时a
是使用未初始化的b
进行初始化,b
是使用未初始化的c
来初始化,最后c
使用66
初始化.
故结果a
和b
都是随机值.
1.2 关键字:explicit
构造函数不仅可以构造与初始化对象,对于以下三种构造函数,还具有类型转换的作用。
- 单个参数构造函数
示例:Test(int a )
- 除第一个参数无默认值其余均有默认值的构造函数.
示例:Test(int a, int b = 66, int c = 88)
- 全缺省的构造函数.
示例:Test(int a=20, int b = 66, int c = 88)
类型转换的情况展示:
令t1=num
,num
将会被赋值给第一个参数.
使用 explicit
后,编译器会报错.
在C++
中,关键字explicit
用来修饰类的构造函数,它的作用是防止隐式类型转换。当一个类的构造函数被声明为explicit
时,编译器将不会自动执行隐式类型转换,而只能进行显式类型转换。这样会提高代码的可读性,隐式类型转换可读性不好.
显示类型转换:↓
附上对应代码:
class Test
{
public:
//1. 单参数构造
//Test(int a )
//{
// _a = a;
//}
//2. 除第一个参数无默认值其余均有默认值的构造函数
//Test(int a, int b = 66, int c = 88)
// : _a(a)
// , _b(b)
// ,_c(c)
//{
//}
//3. 全缺省构造
//Test(int a = 20, int b = 66, int c = 88)
// : _a(a)
// , _b(b)
// , _c(c)
//{
//}
explicit Test(int a=20, int b = 66, int c = 88)
: _a(a)
, _b(b)
,_c(c)
{
}
void Print()
{
cout << _a << endl;
cout << _b << endl;
cout << _c << endl;
}
private:
int _a;
int _b;
int _c;
};
void test1()
{
Test t1;
t1.Print();
cout << endl;
int num = 99;
t1 =(Test) num;
t1.Print();
}
int main()
{
test1();
return 0;
}
二、Static
成员变量/函数
(1)定义
静态成员变量和静态成员函数是属于类而不是对象的成员。它们与类的实例对象无关,而是与整个类相关联。
静态成员变量(static member variable)是在类中使用关键字static
声明的成员变量。它不属于类的任何特定实例对象,而是属于整个类。只会有一个静态成员变量的副本被共享给所有的类的实例对象。可以直接通过类名访问静态成员变量,也可以通过类的对象进行访问。
静态成员函数(static member function)是通过关键字static
声明的类成员函数。与普通成员函数不同,静态成员函数不依赖于类的实例对象。它只能访问类的静态成员,不能访问非静态成员。静态成员函数可以直接通过类名进行调用,而不需要创建类的实例对象。
(2)静态成员函数为什么一定要在类外面初始化:
-
存储空间分配:
静态成员变量
需要在内存中分配存储空间,类的定义只是描述了该成员变量的类型和访问方式,只是图纸,并没有分配空间。所以在类外进行初始化方便为其分配存储空间。 -
只能初始化一次:
静态成员变量
属于整个类,不属于某个对象,静态成员变量
在整个类的生命周期中只能被初始化一次。如果在类的定义中进行初始化,那么每个包含该类定义的文件都会进行初始化,这违背了静态成员变量只应初始化一次的原则。将初始化操作移到类外,可以确保只有一次初始化。 -
存储空间的链接性:将静态成员变量的初始化放在类外,可以保持存储空间的链接性。如果多个不同的源文件都包含了该类的定义并进行了初始化,链接器无法确定使用哪个初始化值,从而导致链接错误。将初始化放在类的实现文件中,可以避免链接错误。
总结:
静态成员变量
和静态成员函数
特点如下:
-
静态成员为所有类对象所共享,不属于某个具体的对象,存放在静态区.
-
静态成员变量必须在类外定义,类中只是声明,定义时指定类域,并且不需要
static
关键字. -
访问方式(前提是公有,如果是私有,需要在类中定义一个函数去返回):
(1)类名::静态成员
(2)对象.静态成员 (不推荐) -
静态成员函数不属于某个对象,所以没有隐藏的
this
指针,不能访问任何非静态成员.
-
静态成员也是类的成员,受
public
、protected
、private
访问限定符的限制
静态成员变量和静态成员函数的主要用途包括:
- 对象计数器:可以使用静态成员变量来实现某个类的对象的计数功能。
class Test
{
public:
Test()//构造函数
{
++_count;
}
Test(Test& t)//拷贝构造
{
++_count;
}
~Test()
{
--_count;
}
static int GetCount()
{
return _count;
}
private:
static int _count;
};
int Test::_count = 0;
void test1()
{
cout << Test::GetCount() << endl;
Test t1,t2;
Test t3(t1);
Test t4;
cout << Test::GetCount() << endl;
}
- 共享数据:静态成员变量可以用于在类的所有实例对象之间共享某些数据。
- 工具函数:静态成员函数可以作为工具函数,独立于对象的操作,提供一些辅助功能。
静态成员变量
和静态成员函数
为类提供了与类相关的特性和功能,并且可以在不创建类的实例对象的情况下进行访问和使用。
- 静态成员函数可以调用非静态成员函数吗?
不可以,静态成员函数不能直接调用非静态成员函数。因为静态成员函数是属于类的,而非静态成员函数是属于对象的。静态成员函数没有指向具体对象的指针,因此不能访问对象的非静态成员函数和非静态成员变量。如果需要在静态成员函数中调用非静态成员函数,可以先创建一个对象,然后通过对象调用非静态成员函数。
- 非静态成员函数可以调用类的静态成员函数吗?
可以,非静态成员函数可以调用类的静态成员函数。静态成员函数是与类相关联的函数,而不是与类的任何特定对象相关联的函数。因此,非静态成员函数可以使用类的静态成员函数,因为静态成员函数不依赖于特定对象的存在。
三、 友元
(1) 友元函数
当我们需要实现流运算符重载
时,会出现一个比较尴尬的问题,那就是第一个参数被this
指针占据,且无法改变,这就造成左操作数是对象,调用起来十分别扭.
示例:
class Date
{
public:
Date(int year = 2023, int month = 1, int day = 1)
:_year(year)
, _month(month)
, _day(day)
{
_month = 2;
}
//第一个参数被this指针占据了,所以ostream& _cout只能作为右操作数,则调用起来就很别扭.
ostream& operator<<(ostream& _cout)
{
_cout << _year << "-" << _month << "-" << _day << endl;
return _cout;
}
private:
int _year;
int _month;
int _day;
};
void test1()
{
Date d1;
d1 << cout;//别扭的调用
}
int main()
{
test1();
return 0;
}
由于类的成员函数第一个参数被this
指针占据,所以流运算符重载只能写成全局函数,但是全局函数无法访问类中的私有成员,为了能够在类的外面也可以访问类中的私有成员.
友元函数的出现,以朋友的身份,去家(类)里参观.
class Date
{
public:
friend ostream& operator<<(ostream& _cout, const Date& d);//友元函数只是一个声明,不受public,private等访问限定符影响,是在类外面的定义的.
Date(int year = 2023, int month = 1, int day = 1)
:_year(year)
, _month(month)
, _day(day)
{
_month = 2;
}
private:
int _year;
int _month;
int _day;
};
ostream& operator<<(ostream& _cout, const Date& d)//这是类外面的函数,没有this指针
{
_cout << d._year << "-" << d._month << "-" << d._day;
return _cout;
}
void test1()
{
Date d1;
cout<<d1<<endl;//顺眼的调用
}
这么说吧.友元函数是类的关系户,类外面别的函数都受类域的限制,不能访问类中的私有成员和保护成员,但是友元函数却可以,一个特殊的存在,由于这样操作破坏了类的封装性,我们建议少使用友元.
小结:
- 友元函数可访问类的私有(
private
)和保护(protect
)成员,但友元函数不属于类,不是类的成员函数. - 友元函数不能用
const
修饰 - 因为友元函数不属于类,所以不受
public
,private
等访问限定符影响,只是一个声明,在类中的哪出现都可以. - 友元函数的调用与普通函数的调用原理相同
(2)友元类
前面介绍了友元函数,那类也可以是类的友元.
- 友元类的所有成员函数都可以是另一个类的友元函数,都可以访问另一个类中的非公有成员。
- 但是友元关系是单向的,不具有交换性。
示例:如果Date
类是Time
类的友元,即在Time
类中声明,Date
是他的朋友.
则可以在Date
类中直接访问Time
类的私有成员变量,但是在Time
类中是无法访问Date
类中的私有成员的. - 友元关系不能传递.
如果B是A的友元,C是B的友元,则不能说明C时A的友元.就比如.
你朋友的朋友不一定是你的朋友.
class Time
{
public:
friend class Date;//友元类只是一个声明,不受public,private等访问限定符影响,是在类外面的定义的.
Time(int hour=6, int minute=30, int second=30)
{
_hour = hour;
_minute = minute;
_second = second;
}
void Test()
{
cout << d1._year;//报错,无法访问,因为Date类并没有声明Time是自己的友元类
}
private:
int _hour;
int _minute;
int _second;
Date d1;
};
class Date
{
public:
Date(int year = 2023, int month = 1, int day = 1)
:_year(year)
, _month(month)
, _day(day)
{
}
void Print()
{
cout << _year << "-" << _month << "-" << _day << endl;
//可以访问,因为Time类声明了Date是它的友元类
cout << t1._hour << "-" << t1._minute << "-" << t1._second << endl;
}
private:
int _year;
int _month;
int _day;
Time t1;
};
void test1()
{
Date d1;
d1.Print();
}
四、内部类(天生友元)
如果一个类A
它定义在另外一个类B的里面(内部),则类A
是类B
的内部类.
外部类对内部类没有任何特权,但是内部类却是外部类的天生友元.
class Date//外部类
{
public:
Date(int year = 2023, int month = 1, int day = 1)
:_year(year)
, _month(month)
, _day(day)
{
}
private:
int _year;
int _month;
int _day;
static int a;
public:
class Time//内部类
{
public:
void Test(const Date& d1)
{
cout << d1._year << "-" << d1._month << "-" << d1._day << endl;//是外部类的天生友元,可以访问外部类的私有成员
a = 5;//可以直接访问外部类的静态成员变量
}
};
};
int Date::a = 3;
内部类的特点:
- 内部类可以定义在外部类的
public
、protected
、private
中皆可,访问时受域作用限定符的限制. - 外部类并不是包括内部类,即sizeof(外部类)=外部类,内部类只是在外部类的类域中定义,并不占空间.
- 内部类可以直接访问外部类中的
static
成员,不需要外部类的对象/类名。
C++
中内部类用的并不多.
本篇到此结束,觉得不错的小伙伴可以三连支持一下.谢谢.