目录
- 初始化列表
- 单参数构造函数的隐式类型转换
- static成员
- 友元
- 友元函数
- 友元类
- 内部类
- 匿名对象
- 了解:编译器优化
- 练习题
初始化列表
构造函数体中的语句只能将其称为赋初值,而不能称作初始化。因为初始化只能初始化一次,而构造函数体内可以多次赋值。
- 每个成员变量在初始化列表中只能出现一次(初始化只能初始化一次)
- 类中包含以下成员,必须放在初始化列表位置进行初始化:
引用成员变量
const成员变量
自定义类型成员(且该类没有默认构造函数时)
class A
{
public:A(int a = 0):_a(a){}
private:int _a;
};class Date
{
public:// 初始化列表是每个成员定义的地方// 不管你写不写,每个成员都要走初始化列表Date(int year, int month, int day, int& i): _year(year), _month(month),_a(1),_refi(i){// 赋值_day = day;}void func(){++_refi;++_refi;}private:int _year; // 每个成员声明int _month = 1;int _day = 1;// C++11支持给缺省值,这个缺省值给初始化列表// 如果初始化列表没有显示给值,就用这个缺省值// 如果显示给值了,就不用这个缺省值// 以下必须定义时初始化const int _x = 10;int& _refi;A _a;
};
初始化列表是构造函数的一部分
初始化列表是每个成员定义的地方
不管你写不写,每个成员都要走初始化列表
在构造函数的初始化列表阶段,如果不写,对内置类型用随机值去初始化(看编译器,有些编译器会处理成0 ),对于自定义类型,会去调用它的默认构造
初始化列表初始化的顺序跟声明的顺序有关
能用初始化列表就用初始化初始化列
有些场景还是需要初始化列表和函数体混着用
Stack(size_t capacity):_array((DataType*)malloc(sizeof(DataType) * capacity)),_size(0),_capacity(capacity)
{cout << "Stack()" << endl;if (NULL == _array){perror("malloc申请空间失败!!!");return;}memset(_array, 0, sizeof(DataType) * _capacity);
}
A aa1(1);是可以的,还有一种写法:
单参数构造函数的隐式类型转换
单参数
private:int _a;
int可以转换为A,支持隐式类型转换
用2调用A构造函数生成一个临时对象,再用这个对象去拷贝构造aa2
编译器会再优化,优化用2直接构造
A aa2 = 2;
证明:
A& ref1 = 2;//这样写不行
原因是中间生成临时对象,临时对象具有常性,所以ref不可以引用这个临时对象,这是权限的放大,变成 const A& ref1 = 2;就可以了
如果不想让隐式类型发生,加explicit关键字
explicit A(int i):_a(i){cout << "A(int i)" << endl;}
如果是多参数:
C++11 支持多参数的隐式类型转换B bb1(1, 1);B bb2 = { 2, 2 };const B& ref2 = { 3,3 };
匿名对象:
// 有名对象 特点:生命周期在当前局部域A aa6(6);// 匿名对象。特点:生命周期只在这一行A(7);
想只使用一次对象时可以用匿名对象,可以节省代码量
A aa7(7);s.PushBack(aa7);s.PushBack(A(8));
Solution sl;
sl.Sum_Solution(10);Solution().Sum_Solution(100);//Solution()定义了一个没有名字的对象
Date d1(2023, 7, 28);cout << d1;cout << Date(2023, 7, 28);
static成员
声明为static的类成员称为类的静态成员,用static修饰的成员变量,称之为静态成员变量;用static修饰的成员函数,称之为静态成员函数。静态成员变量一定要在类外进行初始化
实现一个类,计算程序中创建出了多少个类对象。
// 累积创建了多少个对象
int n = 0;// 正在使用的还有多少个对象
int m = 0;class A
{
public:A(){++n;++m;}A(const A& t){++n;++m;}~A() {--m;}
private:
};//A& Func(A& aa)
A Func(A aa)
{return aa;
}int main()
{A aa1;A aa2;cout << n << " " << m << endl;// 可能被外面随意修改//--n;//++m;A();cout << n << " " << m << endl;Func(aa1);cout << n << " " << m << endl;return 0;
}
class A
{
public:A(){cout << "A()" << endl;++n;++m;}A(const A& t){++n;++m;}~A(){--m;}private:// 静态成员变量属于所有A对象,属于整个类// 声明// 累积创建了多少个对象 static int n;// 正在使用的还有多少个对象static int m;
};
//定义
int A::n = 0;
int A::m = 0;
不是单单属于某个对象,而是想属于所有A对象,属于整个类
缺省值是在初始化列表阶段使用的,但是静态成员变量不会走初始化列表,初始化列表是某个对象的成员的初始化,静态成员变量属于所有对象,所以不能给缺省值。
这两个静态成员不存在对象里面,存在静态区
因为类里面是声明,所以静态成员要在外面定义,相当于声明和定义分离,不是在类外面访问,在.h里定义会出问题
n和m是公有情况下
int main()
{A aa1;A aa2;cout << A::n << " " << A::m << endl;cout << aa1.n << " " << aa2.m << endl;A* ptr = nullptr;cout << ptr->n << " " << ptr->m << endl;// 可能被外面随意修改A();cout << n << " " << m << endl;Func(aa1);cout << n << " " << m << endl;return 0;
}
n、m属于整个类,可以用作用域限定符去拿到
从底层角度看,n和m不在aa1、aa2、ptr指向的对象里面,在静态区,ptr->n这种方式只是帮助其突破类域,虽然是空指针但是不会报错,有没有解引用要看数据在哪。
如果n和m是私有那上面三种方法都访问不了,可以通过成员函数访问,公有和函数去找m不同的是公有的m可以被修改,函数的方法一般不会去修改m,想修改可以返回引用修改。
int GetM()
{return m;
}void Print()
{cout << m <<" " << n << endl;
}
但调用成员函数需要创建对象
用匿名对象调用函数,也会多创建一个对象出来,干扰逻辑
想不创建对象也能访问——静态成员函数
//静态成员函数的特点:没有this指针
static int GetM()
{return m;
}// ...static void Print()
{// x++; // 不能访问非静态,因为没有thiscout << m <<" " << n << endl;
}
以前需要对象去调用,是因为需要去类里找还有就是要传this指针
A::Print();现在不需要传this指针了,只需要突破类域就可以
静态成员函数不能访问非静态成员,因为没有this指针
小总结:
- 静态成员为所有类对象所共享,不属于某个具体的对象,存放在静态区
- 静态成员变量必须在类外定义,定义时不添加static关键字,类中只是声明
- 类静态成员即可用 类名::静态成员 或者 对象.静态成员 来访问
- 静态成员函数没有隐藏的this指针,不能访问任何非静态成员
- 静态成员也是类的成员,受public、protected、private 访问限定符的限制
友元
友元提供了一种突破封装的方式,有时提供了便利。但是友元会增加耦合度,破坏了封装,所以
友元不宜多用。
友元分为:友元函数和友元类
友元函数
class Date
{friend ostream& operator<<(ostream& _cout, const Date& d);friend istream& operator>>(istream& _cin, Date& d);
};
友元函数可以直接访问类的私有成员,它是定义在类外部的普通函数,不属于任何类,但需要在
类的内部声明,声明时需要加friend关键字。
- 友元函数可访问类的私有和保护成员,但不是类的成员函数
- 友元函数不能用const修饰(没有this指针)
- 友元函数可以在类定义的任何地方声明,不受类访问限定符限制
- 一个函数可以是多个类的友元函数
- 友元函数的调用与普通函数的调用原理相同
友元类
友元类的所有成员函数都可以是另一个类的友元函数,都可以访问另一个类中的非公有成员。
-
友元关系是单向的,不具有交换性。
比如上述Time类和Date类,在Time类中声明Date类为其友元类,那么可以在Date类中直接
访问Time类的私有成员变量,但想在Time类中访问Date类中私有的成员变量则不行。 -
友元关系不能传递
如果C是B的友元, B是A的友元,则不能说明C时A的友元。
class Time
{friend class Date; // 声明日期类为时间类的友元类,则在日期类中就直接访问Time类中的私有成员变量
}
内部类
1、B类受A类域和访问限定符的限制,其实他们是两个独立的类
2、内部类默认就是外部类的友元类
class A
{//public:class B{public:void FuncB(int year){A aa;year=aa.c;_d++;}private:int _b;};void func(){B bb;//bb._b = 1;//这样是不行的}private:const static int _a;static int _d;int c = 2;
};const int A::_a = 1;
int A::_d = 3;int main()
{cout << sizeof(A) << endl;A aa;//A::B bb1;//访问不了return 0;
}
- 内部类可以定义在外部类的public、protected、private都是可以的。
- 注意内部类可以直接访问外部类中的static成员,不需要外部类的对象/类名
(非static成员访问需要对象)。 - sizeof(外部类)=外部类,和内部类没有任何关系。
匿名对象
// 有名对象 特点:生命周期在当前局部域A aa6(6);// 匿名对象。特点:生命周期只在这一行A(7);
想只使用一次对象时可以用匿名对象,可以节省代码量
A aa7(7);s.PushBack(aa7);s.PushBack(A(8));
Solution sl;
sl.Sum_Solution(10);Solution().Sum_Solution(100);//Solution()定义了一个没有名字的对象
Date d1(2023, 7, 28);cout << d1;cout << Date(2023, 7, 28);
匿名对象、临时对象具有常性
void f1(const A& aa = A())
{aa.Print();
}int main()
{A aa1;f1(aa1);f1(A(1));f1(2);f1();A();// const引用会延长匿名对象声明周期// ref出了作用域,匿名对象就销毁了const A& ref = A();A aa2;return 0;
}
匿名对象、临时对象具有常性,不可以修改
所以不能直接用引用接收,应该在引用前在加const才可以
const引用同时可以延长匿名对象的生命周期
了解:编译器优化
C++并没有规定要优化,不过现在大多数编译器都会优化
int main()
{A aa1;f1(aa1);cout << "--------------------------" << endl;// 一个表达式,连续的步骤里面,连续的构造会被合并f1(A(1));cout << "--------------------------" << endl;f1(1);cout << "--------------------------" << endl;A aa2 = 1;cout << "--------------------------" << endl;A aa3 = A(2);return 0;
}
本来一次构造,两次拷贝构造
在函数返回之前,本来要去拷贝临时对象,编译器优化之后
ret2没有优化的原因是:
第一点:同类型才能优化,比如拷贝构造和构造都是构造才能优化,拷贝构造和赋值不能合并
第二点:在两个步骤里,不在同一个步骤里
要构造又要拷贝构造,又在同一个步骤里,就优化成直接构造ret
了解这些是让我们知道怎样写能触发编译器的优化,更高效
练习题
求和OJ题
求1+2+3+…+n,要求不能使用乘除法、for、while、if、else、switch、case等关键字及条件判断语句(A?B:C)。
class Sum{
public:Sum(){_m+=_n;_n++;}static int GetM(){return _m;}
private:
static int _n;
static int _m;
};
int Sum:: _n=1;
int Sum:: _m=0; class Solution {
public:int Sum_Solution(int n) {Sum a[n];return Sum::GetM();}
};
改进:
class Solution {
public:class Sum{public:Sum(){_m+=_n;_n++;}};int Sum_Solution(int n) {Sum a[n];return _m;}
private:
static int _n;
static int _m;
};int Solution:: _n=1;
int Solution:: _m=0;
如果不用内部类,得排除一次构造函数带来的干扰:
class Solution {
public:// class Sum{// public:// Sum()// {// _m+=_n;// _n++;// }// };Solution(){_m+=_n;_n++;}int Sum_Solution(int n) {Solution a[n];return _m;}
private:
static int _n;
static int _m;
};int Solution:: _n=0;
int Solution:: _m=0;