一.初始化列表(是构造函数最重要部分)
1.1初始化列表的存在意义:
初始化列表是真正给对象中的成员变量开辟空间和赋初始值的地方,无论有没有写初始化列表,编译器都会自动生成,并且执行,这就是为什么构造函数会自动给成员变量赋初始值的原因。
1.2初始化列表和构造函数
初始化列表不同于构造函数体内的语句,在构造函数体内,可以反复的对成员变量进行赋值的操作,但是不同于函数体内的语句,开空间和赋初始值的初始化操作只能有一次。
class Date
{
public:
Date(int year,int month,int day)
{
_year = year;
_year = 2024;
_month = month;
_month = 4;
_day = day;
_day = 28;
}
pravite:
int _year;
int _month;
int _day;
}
1.3 初始化列表的格式
格式:以一个冒号开始,接着是以一个逗号分割的数据成员列表,每个成员变量后跟一个括号,括号中写想要赋给成员变量的初始值或者表达式。(具体样式如下所示),当然初始化列表可以和构造函数体内的表达式搭配使用。
class Date
{
public:
Date(int year,int month,int day)
:_year(year)
,_month(month)
{
_day = day;
}
pravite:
int _year;
int _month;
int _day;
}
注意事项:
(1)每个成员变量只能在初始化列表中出现一次。
(2)引用成员变量、const成员变量、自定义类型的成员变量 必须在初始化列表初始化。 这三种类型的变量在定义时编译器一定要初始化,这里的自定义类型的成员变量的类中一定没有默认构造函数,但是可能有带参的构造函数。
(3)初始化的顺序和类中声明成员变量的顺序有关。
(4)构造函数会先运行初始化列表里的内容再走构造函数函数体里的内容
二、隐式类型转换和explict
2.1 单参数的隐式类型转换
对于内置类型来说,如下代码举例子,double形变量 d 在赋值给 整形变量 i 的时候,因为两个变量的类型不一样,编译器会产生一个临时变量,再把这个临时变量拷贝给i。
int i = 1;
double d = 1.1;
i = d;
对于自定义类型,如下代码为例,从原理上来说,第一行是正常定义了一个aa1的对象,但是第二行式用2调用A的构造函数生成了一个临时对象,再用这个临时对象去拷贝构造给aa2。
A aa1(1);
A aa2 = 2;
TIP:那么既然编译器会产生一个临时对象,我们能不能直接引用创建的临时对象呢?
答案是直接接收是不可以的,因为编译器生成的临时变量具有常性,直接接受会导致权限的放大。
A aa1(1);
A aa2 = 2;
A& a3 = 3; //错误!错误!错误! 编译器会这样生成一个临时变量 A tmp(3); 但是返回值有常性
正确的接收方法应该这样写:
A aa1(1);
A aa2 = 2;
const A& a3 = 3; //编译器会这样生成一个临时变量 A tmp(3); 但是返回值有常性
2.2 单参数的隐式类型转换的适用案例
在链表S中的每一个节点都是A类型的对象,我们可以定义好A类型的对象,插入到链表中,也可以直接使用隐式类型转换的方式插入,但是注意在PushBack函数中的形参一定是const的引用类型。
2.3 多参数的隐式类型转换
现在在C++11及以上的版本都是支持多参数隐式类型转换的,其写法是在需要传输的实参外面加一个花括号{ },并且每个实参之间用逗号隔开。如下所示,大致原理与单参数隐式类型转换相同。
class B
{
public:
B(int a = 0 ,int b = 0)
:_a = a
,_b = b;
{}
private:
int _a;
int _b;
}
int main()
{
B bb1(1,1);
B bb2 = {2,2};
const B& bb3 = {3,3};
}
2.4 explicit 关键字
如果我们不想让隐式类型转换发生,就需要用explict关键字,用它来修饰构造函数。如下图所示 test_2 = 2在这里首先是想让2转换成一个A类型的临时变量,然后拷贝构造给 test_2,但是explict禁用了这一行为,如果把explict关键字去掉那么这里就不会报错了。
三、匿名对象和有名对象
3.1 匿名对象的特点
匿名对象不像是平常我们写的有名对象一样,有自己的对象名字,生命周期在所在栈帧销毁时结束,但是它虽然没有名字但是可以在隐式类型转换被explict关键字禁用时,依然创建一个临时变量,创建的这个临时对象的生命周期只在创建的那一行!!
class A
{
A(int a)
:_a(a)
{
}
private:
int _a;
}
int main()
{
A test_1(6); //有名对象
A (7); //匿名对象
}
3.2 匿名对象的意义
匿名对象的生命周期既然只有短短的一行,它的存在有什么意义吗?如果我们只想单独使用一个类中的成员函数的话,并且在这里匿名函数可以简化代码,我们在这里就可以使用匿名对象。
class Solution
{
Public:
int sum_solution(int n)
{
int sum = 0 ;
for(int i=0;i<n;i++)
sum+=i;
return sum;
}
}
int main()
{
int ret = Solution(). sum_solution(1000);
cout<<ret<<endl;
return 0;
}
3.3 引用匿名函数
C++中的规则规定可以引用一个匿名对象,因为匿名对象具有常性,所以这里要加const修饰,与此同时这个匿名对象的生命值被延长了,延长至所在栈帧销毁的时候。
int main()
{
const A& test1 = A();
return 0;
}
四、static成员
4.1 static成员的概念
声明为static的类成员被称为类的静态函数,用static修饰的成员变量,称为静态成员变量,static修饰的成员函数,称为静态成员函数。并且静态成员变量一定要在类的外部进行初始化。如下所示,在下面的例子中可以通过 _scount 的数值来判断当前存在着多少AA类的对象。
#include<iostream>
using namespace std;
class AA
{
public:
AA(int n) { ++_scount; }
~AA() { --_scount; }
static int Getscount()
{
return _scount;
}
private:
int _a;
static int _scount;
};
int AA::_scount = 0;
int main()
{
AA test(2);
AA(1);
AA(2);
cout << AA::Getscount() << endl;
}
4.2 static 成员变量的特点
(1)static类型的成员变量不再属于某一个特定的对象,而是属于全局的了。
(2)static类型的成员变量不能给缺省值,它们不存储在对象中,而是存在静态区里。
(3)声明完成员变量后要在类的外部定义,定义时不加static关键字
(4)如果静态成员变量生命在private中的话,一般需要写一个调用函数
(5)静态成员函数没有this指针,不能访问任何非静态成员。
4.3 static 成员函数
静态成员函数是没有this指针的,所以无法使用静态成员函数去访问对象中的成员变量。因此如下案例中,在静态函数Getsount中,是无法返回_a成员变量的。我们在使用静态成员函数时也要写清楚这个静态函数是在哪个域中声明的
#include<iostream>
using namespace std;
class AA
{
public:
AA(int n) { ++_scount; }
~AA() { --_scount; }
static int Getscount()
{
return _a; //报错!!!!!
return _scount;
}
private:
int _a;
static int _scount;
};
int AA::_scount = 0;
int main()
{
AA test(2);
AA(1);
AA(2);
cout << AA::Getscount() << endl;
}
五、内部类
5.1 内部类是什么
定义:如果在一个类的内部定义了另一个类,这个在内部的类被称为内部类,以下是在A类内部定义了另一个B类,这两个类相互独立,sizeof(A)是不会算上B类的空间的。B是A的友元(内部类是外部类的友元类,里面的可以随便访问外面的,但是外面的不能随便访问里面的)。
class A
{
public :
class B
{
private:
int _b;
};
private:
int _a;
};
若是想拿内部类来定义对象得声明清楚在那个类域中
int main()
{
A::B b;
return 0;
}
特点:
(1)内部类可以定义在外部类的public、protected、private都是可以的。
(2)注意内部类可以直接访问外部类中的static成员,不需要外部类的对象/类名。
(3)sizeof(外部类)=外部类,和内部类没有任何关系。