文章目录
- 写在前面
- 1. 进一步认识构造函数
- 1.1 初始化列表
- 1.2 初始化列表的特性
- 1.3 explicit关键字
- 2. static成员变量和static成员函数
- 2.1 static成员的概念
- 2.2 static成员的特性
- 3. 友元
- 3.1 友元函数
- 3.1 友元类
- 4. 内部类
- 5.匿名对象
写在前面
本篇文章详细介绍了C++类和对象中几个重要的概念和特性,包括初始化列表、Static成员、内部类等,下面我们来一一介绍它们。
1. 进一步认识构造函数
通过上篇文章的介绍,我们知道在创建对象时,编译器通过调用构造函数,给对象中各个成员变量一个合适的初始值。
class Date
{
public:
//构造函数
Date(int year = 1900, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
private:
int _year;
int _month;
int _day;
};
虽然上述构造函数调用之后,对象中已经有了一个初始值,但是不能将其称为对对象中成员变量的初始化,构造函数体中的语句只能将其称为赋初值,而不能称作初始化。因为初始化只能初始化一次,而构造函数体内可以多次赋值。
因此,为了给成员变量一个定义初始化的地方,C++规定了成员变量在初始化列表中进行初始化。
1.1 初始化列表
初始化列表:以一个冒号开始,接着是一个以逗号分隔的数据成员列表,每个"成员变量"后面跟一个放在括号中的初始值或表达式。
1.2 初始化列表的特性
-
每个成员变量在初始化列表中只能出现一次(初始化只能初始化一次)。
-
类中包含以下成员,必须放在初始化列表位置进行初始化。
引用成员变量
const成员变量
自定义类型成员(且该类没有默认构造函数时)
-
尽量使用初始化列表初始化,因为不管你是否使用初始化列表,对于自定义类型成员变量,
一定会先使用初始化列表初始化。
上篇文章中提到的在成员变量声明的时候给初始值,如果我们没有使用初始化列表,这个初始值就会在初始化列表的地方使用。
-
成员变量在类中声明次序就是其在初始化列表中的初始化顺序,与其在初始化列表中的先后次序无关。
我们来思考一下下面代码的运行结果是什么?
#include <iostream>
using namespace std;
class A
{
public:
A(int a = 1)
:_a1(a), _a2(_a1)
{
}
void Print()
{
cout << " _a1 = " << _a1 << ' ' << " _a2 = " << _a2 << endl;
}
private:
int _a2;
int _a1;
};
int main()
{
A a;
a.Print();
return 0;
}
运行结果:
我们不禁会发出疑问,为啥_a2的值是随机值呢?
下面我们来分析一下:
1.3 explicit关键字
构造函数不仅可以构造与初始化对象,对于单个参数或者除第一个参数无默认值其余均有默认值
的构造函数,还具有类型转换的作用。
例如下面这种情况:
如果不想发生上述的类型转换,可以使用explicit修饰构造函数,将会禁止构造函数的隐式转换。
#include <iostream>
using namespace std;
class Date
{
public:
/*explicit Date(int year = 1900)
:_year(year)
{
}*/
explicit Date(int year, int month = 1, int day = 1)
:_year(year),_month(month),_day(day)
{
}
Date& operator=(const Date& d)
{
//检查是否是自己给自己赋值
if (this != &d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
}
private:
int _year;
int _month;
int _day;
};
int main()
{
//Date d = 2024;
return 0;
}
2. static成员变量和static成员函数
2.1 static成员的概念
在C++中,static 关键字可以用于声明类的成员变量和成员函数,使它们成为类的静态成员。静态成员属于类本身,而不是类的对象,因此所有属于该类的对象共享同一个静态成员。
声明为static的类成员称为类的静态成员,用static修饰的成员变量,称之为静态成员变量;用static修饰的成员函数,称之为静态成员函数。静态成员变量一定要在类外进行初始化**。
2.2 static成员的特性
-
静态成员为所有类对象所共享,不属于某个具体的对象,存放在静态区。
-
静态成员变量必须在类外定义,定义时不添加static关键字,类中只是声明。
-
类静态成员即可用类名::静态成员或者对象.静态成员来访问。
-
静态成员函数没有隐藏的this指针,不能访问任何非静态成员。
-
静态成员也是类的成员,受public、protected、private 访问限定符的限制。
通过上面的介绍,我们来思考下面两个问题:
静态成员函数可以调用非静态成员函数吗?
非静态成员函数可以调用类的静态成员函数吗?
3. 友元
在 C++ 中,友元是一种机制,允许某些非成员函数或外部类访问一个类的私有成员。通过友元关系,外部函数或类可以直接访问另一个类的私有成员,而无需通过公有接口。友元提供了一种突破封装的方式,有时提供了便利。但是友元会增加耦合度,破坏了封装,所以友元不宜多用。
友元分为:友元函数和友元类。
3.1 友元函数
之前我们想打印Date类的成员,是需要自己实现一个Print函数。那能不能使得Date类像内置类型一样,使用cout实现呢?
答案是可以的,这里重载一下流插入(<<)操作符就可以实现。
下面我们来重载一下Date类的流插入(<<)操作符。
#include <iostream>
using namespace std;
class Date
{
public:
Date(int year = 1900, int month = 1, int day = 1)
:_year(year), _month(month), _day(day)
{}
//重载一下流插入<<操作符
ostream& operator<<(ostream& out)
{
out << _year << '/' << _month << '/' << '_day' << endl;
return out;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1(2024, 2, 7);
Date d2(2024, 2, 8);
cout << d1 << d2 << endl;
return 0;
}
现在有个问题:现在尝试去重载operator<<,然后发现没办法将operator<<重载成成员函数。运行上面代码报错。
因为cout的输出流对象和隐含的this指针在抢占第一个参数的位置。this指针默认是第一个参数也就是左操作数了。但是实际使用中cout需要是第一个形参对象,才能正常使用。所以要将operator<<重载成全局函数。但又会导致类外没办法访问成员。
此时就需要友元来解决。operator>>同理。友元函数可以直接访问类的私有成员,它是定义在类外部的普通函数,不属于任何类,但需要在类的内部声明,声明时需要加friend关键字。
友元函数特性:
- 友元函数可访问类的私有和保护成员,但不是类的成员函数。
- 友元函数不能用const修饰。因为友元函数不是类的成员函数,且没有隐藏的this指针,而const是修饰类的成员函数,修饰的是this指针。
- 友元函数可以在类定义的任何地方声明,不受类访问限定符限制。
- 一个函数可以是多个类的友元函数。
- 友元函数的调用与普通函数的调用原理相同。
3.1 友元类
友元类是指在类中通过 friend 关键字声明的外部类,外部类可以访问该类的私有成员和受保护成员。
现在将友元声明注释掉,编译器就会报错。
友元类特性:
- 友元类的所有成员函数都可以是另一个类的友元函数,都可以访问另一个类中的非公有成员。
- 友元关系是单向的,不具有交换性。比如上述A类和B类,在B类中声明A类为其友元类,那么可以在A类中直接访问B类的私有成员变量,但想在B类中访问A类中私有的成员变量则不行。友元关系不能传递如果C是B的友元, B是A的友元,则不能说明C时A的友元。
- 友元关系不能继承,在继承位置再给大家详细介绍。
4. 内部类
如果在一个类定义在另一个类的内部,这个内部类就叫做内部类。内部类是一个独立的类,它不属于外部类,更不能通过外部类的对象去访问内部类的成员。外部类对内部类没有任何优越的访问权限。
注意:内部类就是外部类的友元类,参见友元类的定义,内部类可以通过外部类的对象参数来访问外部类中的所有成员。但是外部类不是内部类的友元。
内部类特性:
-
内部类可以定义在外部类的public、protected、private都是可以的,但是受类域和访问限定符的限制。
-
注意内部类可以直接访问外部类中的static成员,不需要外部类的对象/类名。
-
sizeof(外部类)=外部类,和内部类没有任何关系。
5.匿名对象
匿名对象的概念:
匿名对象是指在不使用变量名的情况下创建的临时对象,它在创建后立即被使用,通常用于简化代码或执行一次性操作。匿名对象可以在需要对象的地方直接创建并使用,而无需定义变量名。
那么我们该如何定义一个匿名对象呢?
匿名对象的特性:
- 定义匿名对象的时候不用给对象取名字。
- 生命周期只在定义的一行,程序执行到下一行他就会自动调用析构函数。
匿名对象使用场景:
匿名对象在这样场景下就很好用,当然还有一些其他使用场景,这个我们以后遇到了再说。
至此,本片文章就结束了,若本篇内容对您有所帮助,请三连点赞,关注,收藏支持下。
创作不易,白嫖不好,各位的支持和认可,就是我创作的最大动力,我们下篇文章见!
如果本篇博客有任何错误,请批评指教,不胜感激 !!!