深入构造函数:
在前面的学习中已经初步了解了什么是构造函数,现在进行更深入的理解.
构造函数初始化还有⼀种⽅ 式,就是初始化列表,初始化列表的使⽤⽅式是以⼀个冒号开始,接着是⼀个以逗号分隔的数据成 员列表,每个"成员变量"后⾯跟⼀个放在括号中的初始值或表达式。
每个成员变量在初始化列表中只能出现⼀次
引⽤成员变量,const成员变量,没有默认构造的类类型变量,必须放在初始化列表位置进⾏初始 化,否则会编译报错
C++11⽀持在成员变量声明的位置给缺省值,这个缺省值主要是给没有显⽰在初始化列表初始化的成员使⽤的。
如下所示:
//class Time//自定义类型
//{
//public:
// Time(int hour)//没有默认构造的类类型变量(int hour=1为默认构造)
// :_hour(hour)//默认构造就是不用传参就可以调用的构造
// {
// cout << "Time()" << endl;
// }
//private:
// int _hour;
//};
//class Date
//{
//public:
// Date(int& xx, int year, int month, int day)
// :_year(year)//初始化列表的使⽤⽅式是以⼀个冒号开始,
// //接着是⼀个以逗号分隔的数据成员列表,每个"成员变量"后⾯跟⼀个放在括号中的
//初始值或表达式。
// ,_month(month)
// ,_day(day)
// ,_n(1)
// ,_ref(xx)
// ,_t(1)
// ,_ptr((int*)malloc(12))
// {
// if (_ptr == nullptr)
// {
// perror("malloc fail");
// }
// else
// {
// memset(_ptr, 0, 12);
// }
// }
//
// void Print() const
// {
// cout << _year << "-" << _month << "-" << _day << endl;
// }
//
//private:
// // 声明
// int _year;
// int _month;
// int _day;
//
// // error C2512: “Time”: 没有合适的默认构造函数可用
// // error C2530 : “Date::_ref” : 必须初始化引用
// // error C2789 : “Date::_n” : 必须初始化常量限定类型的对象
//
// //引⽤成员变量,const成员变量,没有默认构造的类类型变量,必须放在初始化列表位置进⾏初始化,否则会编译报错
// const int _n; //const
// int& _ref;//引用
// Time _t;//没有默认构造
// int* _ptr;
//};
//int main()
//{
// int x = 0;
// // 对象定义
// Date d1(x, 2024, 7, 14);
// d1.Print();
//
// //const int x = 1;
// // x = 1;
//
// //int& rx;
//
// return 0;
//}
尽量使⽤初始化列表初始化,如果这个成员在声明位置给了缺省值,初始化列表会⽤这个缺省值初始化。如果你没有给缺省值,对于没 有显⽰在初始化列表初始化的内置类型成员是否初始化取决于编译器。对于没有 显⽰在初始化列表初始化的⾃定义类型成员会调⽤这个成员类型的默认构造函数,如果没有默认构 造会编译错误。
//class Time
//{
//public:
// Time(int hour=1)
// :_hour(hour)
// {
// cout << "Time()" << endl;
// }
//private:
// int _hour;
//};
//
//class Date
//{
//public:
// Date(int year = 1, int month = 1, int day = 1)
// :_year(year)
// ,_month(month)
// {}
//
// void Print() const
// {
// cout << _year << "-" << _month << "-" << _day << endl;
// }
//
//private:
// //C++11⽀持在成员变量声明的位置给缺省值,这个缺省值主要是给没有显⽰在初始化列表初始化的成员使⽤的。
// int _year = 1;
// int _month = 1;
// int _day;
//
// int* _ptr = (int*)malloc(12);
// Time _t = 1;
// /*如果这个成员在声明位置给了缺省值,初始化列表会⽤这个缺省值初始化。如果你没有给缺省值,对于没有显⽰在初始化列表初始化的内置类型成员是否初始化取决于编译器,(随机值),C++并没有规定。
//对于没有显⽰在初始化列表初始化的⾃定义类型成员会调⽤这个成员类型的默认构造函数,如果没有默认构造会编译错误.*/
//};
//int main()
//{
// // 对象定义
// Date d1(2024, 7, 14);
// d1.Print();
//
// //Date d2;
// //d2.Print();
// return 0;
//}
初始化列表中按照成员变量在类中声明顺序进⾏初始化,跟成员在初始化列表出现的的先后顺序⽆ 关。建议声明顺序和初始化列表顺序保持⼀致.
//class A
//{
//public:
// A(int a)
// :_a1(a)
// , _a2(_a1)
// {}
// void Print() {
// cout << _a1 << " " << _a2 << endl;
// }
//private:
// int _a2 = 2;//因为a2是第一个声明,所以先给a2初始化,再给a1初始化,a2为随机值.a1(a),而a为1,所以a1=1,
// int _a1 = 2;
//};
//int main()
//{
// A aa(1);
// aa.Print();
//}
类型转换:
C++⽀持内置类型隐式类型转换为类类型对象,需要有相关内置类型为参数的构造函数
//class A
//{
//public:
// // 构造函数前面加explicit就不再⽀持隐式类型转换
// // explicit A(int a1)
// A(int a1)
// :_a1(a1)
// {}
// void Print()
// {
// cout << _a1 << " " << _a2 << endl;
// }
//private:
// int _a1 = 1;
// int _a2 = 2;
//};
//int main()
//{
// A aa1( 1);//构造
// aa1.Print();
//
// // 1先构造⼀个A的临时对象,再⽤这个临时对象拷⻉构造aa1
// // 编译器遇到连续构造+拷⻉构造->优化为直接构造
// A aa1 = 1;//类类型转换
// aa1.Print();
//
// const A& aa2 = 1;//属于类型转换,中间会产生临时对象,临时对象具有常性,所以要加const,
//
// return 0;
//}
static成员:
⽤static修饰的成员变量,称之为静态成员变量,静态成员变量⼀定要在类外进⾏初始化。
具体特点见代码的注释.
//class A
//{
//public:
// A()
// {
// ++_scount;
// }
//
// A(const A& t)
// {
// ++_scount;
// }
//
// ~A()
// {
// --_scount;
// }
//
// static int GetACount()//静态成员函数(没有this指针)
// {
// //_a++;静态成员函数中可以访问其他的静态成员,但是不能访问⾮静态的,因为没有this指针。所以在此处_a++报错
// return _scount;//ok
// }
//
// void func()//⾮静态的成员函数,可以访问任意的静态成员变量和静态成员函数。
// {
// cout << _scount << endl;
// cout << GetACount() << endl;
// }
//
//private:
// // 类里面声明
// static int _scount; //静态成员变量(全局)为所有类对象所共享,不属于某个具体的对象,不存在对象中,存放在静态区。
//
//
// int _a = 1;//静态成员函数中可以访问其他的静态成员,但是不能访问⾮静态的,因为没有this指针。
//};
//
类外面初始化
//int A::_scount = 0;
//
//int main()
//{
// //cout << A::_scount << endl;//为私有(private) ,不能访问
// //cout << sizeof(A) << endl;
//
// cout << A::GetACount() << endl;//0
//
// A a1, a2;
// A a3(a1);
// cout << A::GetACount() << endl;//3
//
// cout << A::GetACount() << endl;//2 突破类域(知道去哪里找)就可以访问静态成员,可以通过类名::静态成员 或者 对象.静态成员 来访问静态成员变量和静态成员函数。
// cout << a1.GetACount() << endl;//2
//
// return 0;
//}
小练习:
设已经有A,B,C,D 4个类的定义,程序中A,B,C,D构造函数调⽤顺序为?(E)
程序中A,B,C,D析构函数调⽤顺序为?(B)
A:D B A C B:B A D C C:C D B A D:A B D C E:C A B D F:C D A B C
C c;
int main()
{
A a;
B b;
static D d;//周期是全局的,优先局部的,再是全局的
return 0;
}
构造是按照顺序构造的,析构是后定义的先析构.在成员变量前加static表示他的周期是全局的.
右元:
友元提供了⼀种突破类访问限定符封装的⽅式,友元分为:友元函数和友元类,在函数声明或者类 声明的前⾯加friend,并且把友元声明放到⼀个类的⾥⾯。
在某个类中,在谁前面加friend,表示其可以访问该类中的私有成员变量.
具体特点见代码注释:
class B;// 前置声明,否则A的友元函数声明编译器不认识B
class A
{
// 友元声明
friend void func(const A& aa, const B& bb);
private:
int _a1 = 1;
int _a2 = 2;
};
class B
{
// 友元声明
friend void func(const A& aa, const B& bb);
private:
int _b1 = 3;
int _b2 = 4;
};
void func(const A& aa, const B& bb)//外部友元函数可访问类的私有和保护成员,友元函数仅仅是⼀种声明,他不是类的成员函数,例如func里面可以访问A和B类里面的const A& aa, const B& bb
{
cout << aa._a1 << endl;//func想访问类里面的私有,所已在类里面添加friend void func(const A& aa, const B& bb);
cout << bb._b1 << endl;
}
int main()
{
A aa;
B bb;
func(aa, bb);
return 0;
}
友元类中的成员函数都可以是另⼀个类的友元函数,都可以访问另⼀个类中的私有和保护成员。
class A
{
// 友元声明
friend class B;//B是A的右元
private:
int _a1 = 1;
int _a2 = 2;
};
class B//B可以访问A里面的私有(B是A的右元)
{
public:
void func1(const A& aa)
{
cout << aa._a1 << endl;
cout << _b1 << endl;
}
void func2(const A& aa)
{
cout << aa._a2 << endl;
cout << _b2 << endl;
}
private:
int _b1 = 3;
int _b2 = 4;
};
内部类 :
如果⼀个类定义在另⼀个类的内部,这个内部类就叫做内部类。内部类是⼀个独⽴的类,跟定义在 全局相⽐,他只是受外部类类域限制和访问限定符限制,所以外部类定义的对象中不包含内部类。 内部类默认是外部类的友元类。
class A
{ //虽然B类在A里面,大小是A类的大小,(不包含B类的大小)因为只有一个整型,所以为4字节.
private:
static int _k;
int _h = 1;
public:
class B //B默认就是A的友元,可以访问A的私有.
{
public:
void foo(const A & a)
{
cout << _k << endl; //OK
cout << a._h << endl; //OK
}
};
};
int A::_k = 1;
int main()
{
cout << sizeof(A) << endl;
A::B b;
A aa;
b.foo(aa);
return 0;
}
匿名对象:
⽤ 类型(实参) 定义出来的对象叫做匿名对象,相⽐之前我们定义的类型 对象名(实参) 定义出来的 叫有名对象 •
匿名对象⽣命周期只在当前⼀⾏,当用完之后就会调用析构.
class A
{
public:
A(int a = 0)
:_a(a)
{
cout << "A(int a)" << endl;
}
~A()
{
cout << "~A()" << endl;
}
private:
int _a;
};
class Solution
{
public:
int Sum_Solution(int n)
{
//...
return n;
}
};
int main()
{
A aa1;//有名对象
// 不能这么定义对象,因为编译器⽆法识别下⾯是⼀个函数声明,还是对象定义
//A aa1();
// 但是我们可以这么定义匿名对象,匿名对象的特点不⽤取名字,
// 但是他的⽣命周期只有这⼀⾏,我们可以看到下⼀⾏他就会⾃动调⽤析构函数
A();
A(1);
Solution st;
cout << st.Sum_Solution(10) << endl;//不使用匿名对象
// 匿名对象在这样场景下就很好⽤,
Solution().Sum_Solution(10);//使用匿名对象
return 0;
}