构造函数体中的语句只能将其称为赋初值,而不能称作初始化。因为初始化只能初始化一次,而构造函数体内可以多次赋值。
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修饰的成员函数,称之为静态成员函数。静态成员变量一定要在类外进行初始化
实现一个类,计算程序中创建出了多少个类对象。
// 累积创建了多少个对象
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++; // 不能访问非静态,因为没有this
cout << m <<" " << n << endl;
}
以前需要对象去调用,是因为需要去类里找还有就是要传this指针
A::Print();现在不需要传this指针了,只需要突破类域就可以
静态成员函数不能访问非静态成员,因为没有this指针
小总结:
友元提供了一种突破封装的方式,有时提供了便利。但是友元会增加耦合度,破坏了封装,所以
友元不宜多用。
友元分为:友元函数和友元类
class Date
{
friend ostream& operator<<(ostream& _cout, const Date& d);
friend istream& operator>>(istream& _cin, Date& d);
};
友元函数可以直接访问类的私有成员,它是定义在类外部的普通函数,不属于任何类,但需要在
类的内部声明,声明时需要加friend关键字。
友元类的所有成员函数都可以是另一个类的友元函数,都可以访问另一个类中的非公有成员。
友元关系是单向的,不具有交换性。
比如上述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;
}
// 有名对象 特点:生命周期在当前局部域
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;