一、const取地址操作符重载
这两个默认成员函数一般不需要定义,编译器会默认自动生成。
我们如果不实现的话编译器会自动实现,我们自己显式实现的话编译器就不会自动生成。
我们来看下以下代码实例:
#include <iostream>
using namespace std;
class Date
{
public:
//取地址符重载
Date* operator&()
{
return this;
}
const Date* operator&() const
{
return this;
}
private:
int _year;
int _month;
int _day;
};
int main() {
return 0;
}
这两个运算符一般不需要我们自己显式的进行实现,使用编译器默认生成的就行。
注意:运算符重载中,参数的顺序和可操作数的顺序是一致的。
const放在函数名的后面是修饰*this,保证*this不能被改变。
结论:流插入想重载为成员函数,可以但是用起来不符合正常逻辑,因此不建议将流插入重载成成员函数,建议重载成全局函数。
二、再谈构造函数
2.1 构造函数体赋值
之前我们已经介绍了,在创建对象时,编译器会通过调用构造函数,给对象中各个成员变量进行一个初始化。
我们再来看下这个的代码实例:
#include <iostream>
using namespace std;
class Date
{
public:
Date(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
private:
int _year;
int _month;
int _day;
};
int main() {
return 0;
}
虽然上边的构造函数调用后,对象中已经有了一个初始值,但是不能将其称为对象中的成员变量的初始化,构造函数中的语句只能将其称为初赋值,而不能称作初始化,因为初始化只能初始化一次而构造函数体内可以进行多次赋值。
2.2 初始化列表
初始化列表:以一个冒号开始,接着是一个以逗号分隔的数据成员列表,每个“成员变量”后面都跟着一个放在括号中的初始值或者表达式。
来看下下面的例子:
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.类中包含以下成员,必须放在初始化列表的位置进行初始化
(1)引用成员变量
(2)const成员变量
(3)自定义类型成员(且类里面没有默认构造函数时)
const和引用类型必须在定义的地方初始化。
没有默认构造函数的自定义类型成员必须显式传参调用构造函数
我们来看下代码实例:
class A
{
public:
A(int a)
:_a(a)
{}
private:
int _a;
};
class B
{
public:
B(int a, int ref)
:_aobj(a)
,_ref(ref)
,_n(10)
{}
private:
A _obj; //没有默认构造函数
int& _ref;//引用
const int _n;//const
};
在这里时先走初始化列表,然后再走函数体
3.尽量使用初始化列表进行初始化,因为不管我们是否使用初始化列表,对于自定义类型成员变量,一定会先走初始化裂变进行初始化。
注意:初始化列表,不管写不写,每个成员变量都会先走一遍。
自定义类型的成员会调用默认构造函数(没有默认构造函数就会编译报错)
缺省值是给初始化列表使用的,我们再类中定义变量时给一个缺省值,实际上就是走了初始化列表。
若是没有显式的写初始化列表则用缺省值,内置类型有缺省值用缺省值,没有的话看编译器的实现是否处理。
class Time
{
Time(int hour = 0)
:_hour(hour)
{
cout << "Time()" << endl;
}
private:
int _hour;
};
4.成员变量在类中声明次序就是其在初始化列表中的初始化顺序,与其在初始化列表中的先后次序无关。
我们来看下下面的代码:
class A
{
public:
A(int a)
:_a1(a)
,_a2(_a1)
{}
void Print()
{
cout << _a1 << " " << _a2 << endl;
}
private:
int _a2;
int _a1;
};
int main() {
A aa(1);
aa.Print();
return 0;
}
我们来看一看这段代码的输出结果:

在clion编译器下输出的结果是1,0,那么为什么不是1,1 呢是因为我们在类中声明的顺序是a2, a1,在初始化时先初始化a2但是这是a1还没有初始化是随机值,但是这里编译器处理成了0,然后a1用a进行初始化是1。
2.3 explicit关键字
构造函数不仅可以构造与初始化对象,对于单个参数或者除了第一个参数无默认值其余均有默认值的构造函数,还具有类型转换的作用。
我们先来看一段代码实例:
#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
using namespace std;
class Date
{
public:
//1.单参数构造函数,没有使用explicit修饰时具有类型转换作用
//explicit 修饰构造函数,禁止类型转换 explicit去掉后这段代码可以编译
explicit Date(int year)
:_year(year)
{}
//2.虽然有多个参数,但是创建对象时后两个对象可以不传递,没有使用explicit修饰具有类型转换的作用
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._year;
_day = d._day;
}
return *this;
}
private:
int _year;
int _month;
int _day;
};
void test()
{
Date d1(2023);
//用一个整型变量给日期类型对象赋值
//实际编译器背后会用2024构造一个无名对象,最后用对象给d1对象进行赋值
d1 = 2024;
}
int main()
{
test();
return 0;
}
这里多参数的类型转换赋值可以像下面那样写:
A aa1(1);
A aa2(1, 2);
A aa3 = { 1, 2 };
A aa4{ 1, 2 };
多参数的构造也支持自定义类型到内置类型之间的隐式类型转换。
缺省值可以直接给值,也可以malloc或者隐式类型转换。
使用explicit修饰构造函数,会禁止构造函数的隐式转换。
三、static成员
2.1 概念
声明为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;
A a3(a1);
cout << A::GetAcount() << endl;
}
静态成员函数的定义不能给缺省值,因为缺省值是给初始化列表使用的,它在静态区不在对象中,不走初始化列表,属于整个类属于所有对象。
静态成员函数:static修饰成员函数,无this指针,只能访问静态成员
因此结果是0和3
2.2 static成员的特性
1.静态成员为所有类对象所共享,不属于某个具体的对象,存放在静态区
2.静态成员变量必须在类外定义,定义时不添加static关键字,类中只是声明
3.类静态成员即可用 类名::静态成员 或者对象.静态成员 来访问
4.静态成员函数没有隐藏的this指针, 不能访问任何非静态成员
5.静态成员也是类的成员,受到public, protected,private这些访问限定符的限制
结语:
这篇博客到这里就结束了,介绍了c++中关于类和对象之中更深入的知识,希望大家能从中有所收获。