目录
一、初始化列表
二、隐式类型转换
三、静态成员
四、友元
1、友元函数
2、友元类
五、内部类
六、匿名对象
一、初始化列表
下面是日期类的一个构造函数,调用该构造函数后,对象中已经有了一个初始值,但并不能将它称为对对象成员变量的初始化,构造函数体中的语句只能将其称为赋初值,而不是初始化,因为初始化只能有一次,但构造函数体内可以有多次赋值。
class Date
{
public:
Date(int year,int month,int day)
{
_year = year;
_month = month;
_day = day;
}
private:
int _year;
int _month;
int _day;
};
初始化列表:以一个冒号开始,接着是以逗号分隔的数据成员列表,每个成员变量后跟一个放在括号内的初始值或表达式。
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)const修饰的成员变量(特征:必须在定义的时候初始化)
(3)自定义类型成员(且该类没有默认构造函数时)
如果自定义类型没有默认构造函数(无参、全缺省、编译器自动生成的构造函数都叫默认构造函数)时,没有进行初始化,编译器就会报错,如下:
class A
{
public:
A(int a)
:_a(1)
{}
private:
int _a;
};
class B
{
public:
//初始化列表:对象的成员定义的位置
B(int a, int& ref)
:_ref(ref)
,_n(1)
{}
private:
int& _ref;//引用
const int _n;//const
A _aobj;//没有默认构造函数
int x = 1;//缺省值,初始化列表没写时,会用缺省值
};
int main()
{
int n=10;
B bb1(12, n);//对象整体定义
return 0;
}
因此,我们要在初始化列表对其进行手动初始化:
B(int a, int ref)
:_ref(ref)
,_n(1)
,_aobj(1)//调用对象_aobj的构造函数
{}
如果一个类中有自定义类型的成员变量,不论你是否使用初始化列表,这些自定义类型的成员变量都会先使用默认构造函数进行初始化。
如果需要自定义初始化这些成员变量,那么只有在构造函数的初始化列表中进行初始化才是有效的。
初始化列表并不能完全取代函数体,举例,对于_a的malloc的检查就需要在函数体内检查。
class Stack
{
public:
Stack(int capacity = 10)
: _top(0)
,capacity(capacity)
,_a((int*)malloc(capacity * sizeof(int)))
{
if (nullptr == _a)
{
perror("malloc fail");
return;
}
}
private:
int capacity;
int _top;
int* _a;
};
成员变量在类中的声明次序就是其在初始化列表中的初始化顺序,与其在初始化列表中的先后次序无关。如上面的代码如果把成员变量定义顺序改成下面这样,就会出问题。
class Stack
{
public:
Stack(int capacity = 10)
: _top(0)
,capacity(capacity)
,_a((int*)malloc(capacity * sizeof(int)))
{
if (nullptr == _a)
{
perror("malloc fail");
return;
}
}
private:
int* _a;//按这个顺序
int capacity;
int _top;
};
二、隐式类型转换
int i=10;
double d = i;
double& d = i;//不可以
const double& d = i;//可以
上面这行代码发生了隐式类型转换,i赋值给d时,会先产生一个具有常属性的临时变量,再将该变量赋给d。
由于这个临时变量具有常属性,所以不能double& d = i ;因为从一个具有const属性的临时变量到double&是权限放大。
像const double& d = i;这样才是可行的。
A aa1 = 2;
A& aa2 = 2;//不可
const A& aa2 = 2;//可以
同理,如果是A aa1 = 2;
会先用2构造一个临时对象,再将临时对象拷贝赋值给aa1(但是现在的编译器大概率会将其优化,直接用2进行构造)
拷贝对象时的一些编译器的优化:
在传参和传返回值的过程中,一般编译器会做一些优化,减少对象的拷贝,这个在一些场景下是非常有用的。
一个表达式中,连续构造+拷贝构造->优化为直接构造
f1(1);
一个表达式中,连续构造+拷贝构造->优化一个拷贝构造
f1(A(2));
一个表达式中,连续拷贝构造+拷贝构造->优化一个拷贝构造
A aa2 = f2();
一个表达式中,连续拷贝构造+赋值重载->无法优化
aa1 = f2();
三、静态成员
声明为static的类成员称为类的静态成员,用static修饰的成员变量,称为静态成员变量,用static修饰的成员函数,称为静态成员函数。静态成员函数一定要在类外面进行初始化。
特性:
(1)静态成员为所有类对象共享,不属于某个具体的对象,存放在静态区。
(2)静态成员变量必须在类外定义,定义时不添加static关键字,类中只是声明。
(3)类静态成员即可用 类名::静态成员 或者 对象.静态成员 来访问。
(4)静态成员函数没有隐藏的this指针,不能访问任何非静态成员。
(5)静态成员也是类的成员,受public、protected、private访问限定符的限制。
(6)成员变量:属于每个类对象,存储在对象里面。
静态成员变量:属于类,属于类的每个对象共享,存储在静态区,它的声明周期是全局的。
(7)静态成员变量不能给缺省值。
非静态成员函数可以调用静态成员函数。
静态成员函数不能调用非静态成员函数,因为静态成员函数没有this指针。
例题:实现一个类,计算类中定义了多少个类对象。
class A
{
public:
A()
{
++_scount;
}
A(const A&t) {
++_scount;
}
~A() {
--_scount;
}
static int GetACount()
{
//_a++;无法访问非静态成员,因为没有this指针
return _scount;
}
private:
static int _scount;//声明
//static int _scount = 1;不可以给缺省值
int _b;
};
int A::_scount = 0;//类外定义
int main()
{
cout << A::GetACount() << endl;
A a1, a2;
A a3(a1);
cout << A::GetACount() << endl;
return 0;
}
静态成员函数引用场景举例:
如果 要你设计一个类,且在类外面只能在栈区上创建对象
或者 要你设计一个类,且在类外面只能在堆区上创建对象
class A
{
public:
static A GetStackobj()
{
A aa;//栈
return aa;
}
static A* GetHeapobj()
{
return new A;//堆
}
private:
A()
{}
int _a = 1;
};
int main()
{
A::GetHeapobj();
A::GetStackobj();
return 0;
}
四、友元
友元提供了一种突破封装的方式,有时提供了便利。但是友元会增加耦合度,破坏了封装,所以友元不宜多用。
友元分为:友元函数和友元类
1、友元函数
问题:现在尝试去重载operator<<,然后发现没办法将operator<<重载成成员函数。因为cout的输出流对象和隐含的this指针在抢占第一个参数的位置。this指针默认是第一个参数也就是左操作数了。但是实际使用中cout需要是第一个形参对象,才能正常使用。、
所以要将operator<<重载成全局函数。但又会导致类外没办法访问成员,此时需要友元来解决。operator>>同理。详细代码参考上期博文。
友元函数可以直接访问类的私有成员,它是定义在类外部的普通函数,不属于任何类,但需要在类内部声明,声明时加关键字friend。
友元函数可以访问类的私有成员和保护成员,但不是类的成员函数
友元函数不能用const修饰
友元函数可以在类定义的任何地方声明,不受类访问限定符的限制
友元函数的调用与普通函数的调用原理相同
friend ostream& operator<<(ostream& out, const Date& d);
ostream& operator <<(ostream& out, const Date& d)
{
out << d._year << "年" << d._month << "月" << d._day << "日" << endl;
return out;
}
2、友元类
友元类的所有成员函数都可以是另一个类的友元函数,都可以访问另一个类中的非公有成员。
注意:
1、友元关系是单向的。
2、友元关系不能传递。
3、友元关系不能继承。
五、内部类
定义在类内部的类,就叫内部类。
内部类是一个独立的类,不属于外部类,不能通过外部类去访问内部类的成员。外部类对内部类没有任何优越的访问权限。
内部类就是外部类的友元类,所以内部类可以通过外部类的对象参数来访问外部类中的使用成员,但是外部类不是内部类的友元。
(1)内部类可以定义在外部类的public、private、protected
(2)内部类可以直接访问外部类中的static成员,不需要外部类的对象\类名
(3)sizeof(外部类)=外部类,和内部类没有任何关系。
六、匿名对象
有名对象生命周期在当前函数的局部域。
匿名对象的生命周期在当前行。
因为匿名对象具有常性,所以不能直接引用,但可以这样:
虽然匿名对象的作用域只在当前行,但由于const引用会延长匿名对象的生命周期(延长到当前函数局部`域),所以ra并不是野引用。
class Solution
{
public:
int Sum_Solution(int n)
{
cout << "sums" << endl;
return n;
}
};
int main()
{
Solution s1;
s1.Sum_Solution(10);
Solution().Sum_Solution(20);//匿名对象调用函数
return 0;
}