前提:
对象实例化,成员变量就整体定义了,那么成员变量是在哪里单体定义初始化的?构造函数处吗?
概念:初始化列表是每个的成员定义初始化的位置
位置:在构造函数底下
结构: :代表开始 ,代表分点
class Date
{
public:
// // 初始化列表
Date(int year, int month, int day)
: _year(year)
, _month(month)
, _day(day)
{}
}
C++11支持在成员变量声明的地方给缺省值
这个值给的就是初始化列表中括号的位置(没给才会用缺省值)
private:
int a = 1;
int* p1 = nullptr;
int* p2 = (int*)malloc(4);这个地方就是函数参数列表那里的缺省值int * _p = (int *)malloc(4);
Date d1 = 3;
Date d2 = {1, 2, 3};
Date(int year, int month, int day, int& x)
:_year(year)
,_month(month)
,_day(day)
,_n(1)
,_ref(x)
,_aa(1, 2)
,_p((int*)malloc(sizeof(4) * 10))
{
if (_p == nullptr)
{
perror("malloc fail");
}for (size_t i = 0; i < 10; i++)
{
_p[i] = 0;
}
}
//为了更好理解这个初始化列表,就尽量个平时的初始化类比一下就好了
int a = 3;-> :a(3)
:_year(year)
,_month(month)
,_day(day)
,_n(1)
,_ref(x)
,_aa(1, 2)
,_p((int*)malloc(sizeof(4) * 10))可以把这个当成函数的参数列表
如果说某个类支持用单个参数进行构造,就可以利用这个语法一步到位
Date(int month)
{
_month = month;
}Date d1 = 2;
//用2构造成一个临时对象(构造函数),再用这个临时对象拷贝给d1(拷贝构造)(因为是临时对象,所以具有常性,如果想用引用来接收的话要用常引用)
- 虽然上面看起来是构造+拷贝构造,但是编译器会对同一个表达式连续步骤的构造进行合二为一
class Date
{
public:
Date(int year = 100) //单参数构造函数
{
year = _year;
}
void Print();
public:
int _year;
};
---------------------------------------------------------
class Time
{
public:
Time(Date d1)
{
_d1 = d1;
}
private:
Date _d1 = 3; //自定义类型:单参数构造函数支持隐式类型转换
//Date _d1 = 3; ---> Date d3(3);
Date _d1 = d3;
};
原本:
Stack st;
Date d1(3);
st.Push(d1);
现在:
Stack st;
st.Push(3);
C++11支持多参数构造函数的隐式类型转换
用花括号
Date d1 = {2022, 3, 21} //初始化
Date d1 = { 2022,3 ,21 }; //成员变量声明处赋初值
如果你不想让这种(单参数构造函数支持隐式类型转换)隐式类型转化,就可以用一个关键字:explicit
explicit Date(int month)
{
_month = month;
}
被static用来修饰的成员属于整个类,相当于是类自己的全局变量
如果是公有的,可以直接指定类域来访问: Date::count
如果是私有的,就用一个公有的成员函数去访问
当全局变量来用,只不过这个全局变量的访问方式没那么直接,类似于用来构造统计
一个不依赖于对象的成员函数,也没法访问某些对象的成员变量
特点:没有this
因为是公有的,并且不依赖于对象,所以可以直接指定类域来访问: Date::sta_Func()
虽说不依赖对象,但是对象也可以去调用它d1.sta_Func()
因为它不依赖对象,所以不能出现某些依赖于对象的成员变量,只能是有一些静态成员变量
作用
用来专门访问静态成员变量
虽然说可以直接让对象直接调用,但当函数逻辑不依赖对象状态(只需访问静态成员或纯参数计算)时,优先使用静态成员函数。这符合面向对象设计的"高内聚"原则,同时减少不必要的对象耦合
要将这些static和非static映射现实来理解,只有映射现实来理解,才不会因为少用而忘记他们具体的使用场景和作用
想象场景:一家汽车制造厂 (
Car
类)
静态成员变量
(static member variable
):工厂中央控制室的“总产量计数器”
它是什么? 工厂墙上挂着一个巨大的电子显示屏,上面显示着一个数字:当前工厂制造出的所有汽车的总数量。
属于谁? 这个计数器不属于任何一辆具体的汽车。它属于整个工厂本身。它是工厂运营的全局状态。
存在方式: 整个工厂只有一个这样的计数器。无论生产线上正在组装多少辆车,或者停车场里停了多少辆新车,这个计数器只有一个。
谁操作它?
非静态成员函数 (汽车个体的行为): 当一辆新车从生产线下线(对象被创建,构造函数调用)时,这辆新车会“通知”中央控制室:“我诞生了!”。控制室人员(或自动系统)就会把总产量计数器 +1。
当一辆汽车被销毁(对象被析构)时,它会“报告”:“我要报废了!”。控制室人员就把总产量计数器 -1。
静态成员函数 (工厂管理层的操作): 工厂经理可以直接走进中央控制室(无需通过任何一辆具体的汽车),查看这个计数器上的数字,了解当前总共有多少辆车存在 (
getTotalCars()
)。他甚至可以重置它(如果设计允许,但例子中通常不这样做)。关键点: 这个“总产量计数器”(静态变量)是工厂级别的全局信息,所有汽车个体(对象)共享并共同影响它,但它本身独立于任何一辆车。
非静态成员变量
:每辆汽车的“车辆识别号(VIN)”
它是什么? 每辆生产出来的汽车,在出厂时都会被打上一个独一无二的金属铭牌,上面刻着它的序列号(比如第0001号车,第0002号车...)。
属于谁? 这个序列号专属于这辆特定的汽车。它是这辆车的个体身份标识。
存在方式: 每一辆制造出来的汽车都有自己的序列号。停车场里有100辆车,就有100个不同的序列号。
谁操作它? 主要是非静态成员函数 (汽车个体的行为):
车主可以查看自己车的序列号 (
getSerial()
)。维修站可以通过序列号查询这辆车的保养记录。
这个序列号在汽车诞生时(构造函数中),由工厂根据当时的“总产量计数器”的值来赋予(
serialNumber = ++totalCarsCreated
)。一旦赋予,一般就不再改变。
静态成员函数
(static member function
):工厂汽车总数的查询
getTotalCars()
(静态成员函数):
定位: 属于
Car
类本身的全局操作。它的功能是“查询当前存在的Car
总数”。为什么是静态?
它不需要知道任何一辆具体车的信息(不需要访问
serialNumber
或对象的其他非静态成员)。它只关心类级别的状态
totalCarsCreated
。它可以在没有任何
Car
对象被创建的情况下被调用 (如main
函数开头),因为它操作的是类的全局状态,而非对象状态。调用方式清晰:
Car::getTotalCars()
明确表示这是类级别的信息。
非静态成员函数
:每辆汽车自带的“功能按钮”
它是什么? 每辆汽车上都有方向盘、油门、刹车、收音机按钮、车载显示屏等。
功能是什么? 这些按钮/功能操作的是这辆特定的汽车本身:
按方向盘是让这辆车转向 (
turnLeft()
)。踩油门是让这辆车加速 (
accelerate()
)。按收音机按钮是打开这辆车的音响 (
playRadio()
)。在车载显示屏上可以查看这辆车的序列号 (
getSerial()
) 或这辆车的油耗 (getFuelConsumption()
)。关键特点:
你必须“在车里”或“操作这辆车”: 要使用这些功能,你必须有具体的车对象 (
myCar.accelerate()
,yourCar.playRadio()
)。它们操作个体状态: 这些功能直接影响或查询的是你正在操作的那辆具体汽车的状态(速度、位置、音响开关、序列号等)。
它们也能“联系工厂”: 虽然主要功能是操作自身,但车上的某个按钮(比如一个特殊的“工厂信息”按钮)也可以用来查询工厂的总产量(调用静态函数
getTotalCars()
或访问静态变量totalCarsCreated
),就像车载系统可以拨打那个客户服务热线一样。这就是为什么非静态函数能访问静态成员。但它查询到的,仍然是整个工厂的信息,不是这辆车独有的。
定义在类的外部。
本身不是类的成员函数。
其目的是授予这个外部函数访问该类私有 (private
) 和受保护 (protected
) 成员的权限。
一个普通的在类外部的函数,然后在那个类里面去进行友元声明
class Date
{
friend ostream& operator<<(ostream& _cout, const Date& d);
friend istream& operator>>(istream& _cin, Date& d);
pub1ic :
Date(int year = 1900, int month = 1, int day = 1)
: --year(year)
, _month(month)
,_day(day)}
我这个类可能会在任何地方都要访问你的私有和保护,就可以在你的类里面声明友元
class Date
{
friend class Time;
pub1ic :
Date(int year = 1900, int month = 1, int day = 1)
: --year(year)
, _month(month)
,_day(day)}
友元是单向的,你可以访问他的私有,但他不能访问你的私有
在一个类里面嵌套另一个类,两个类之间只是多了友元和域作用限定符的区别,还是两个单独的类
没有名字的对象,即用即销毁
Date();
Date(10);
生命周期只在当前一行
作用
如果你要调用Date里面的一个函数,你还得先创建一个Date对象出来才能调用
Date().Print(1);
匿名对象的作用重点在后面的仿函数
构造+拷贝构造
拷贝构造+拷贝构造
不同的编译器激进程度不同;越激进越有可能出bug(原本该出现的,被优化掉就不出现了)
A aa1 = 2; 构造 + 拷贝构造 --》直接构造
返回值的情况下
拷贝构造+拷贝构造 -> 拷贝构造
aa->tmp->ret :: aa->ret,没有产生临时对象
匿名对象:f4()直接不在函数里产生对象,直接变成ret构造
并没有完整的结论,因为不同编译器的激进程度不一样,debug和release也不一样,release还更激进(在确保没有什么影响的情况下)
int main()
{
A aa1 = 2; // 构造 + 拷贝构造 -》 直接构造
const A& aa2 = 2;
f1(aa1);
f1(A(2)); // 构造 + 拷贝构造 -》 直接构造
f2(aa1);
return 0;
}
A f4()
{
return A();
}
A f3()
{
A aa;
return aa;
}
int main()
{
// 拷贝构造+拷贝构造->拷贝构造
A ret = f3();
// 构造 + 拷贝构造 + 拷贝构造 ->构造
//A ret = f4();
//A ret;
//ret = f4();
return 0;
}