目录
1.初始化列表
2.隐式类型转换
3.static成员
编辑
4.友元与内部类
友元
内部类
5.匿名对象
1.初始化列表
在类和对象中,实现构造函数时,初始化成员变量是用函数体内赋值,其实还有另一种方式初始化成员变量:初始化列表。
- 初始化列表:用一个冒号开始,逗号分隔,每个成员变量后跟一个放在括号里的初始值或表达式。
class Date
{
public:Date(int year, int month, int day)//初始化列表:_year(year) ,_month(month),_day(day){}void Print(){cout << _year << "/" << _month << "/" << _day << endl;}private://年月日int _year;int _month;int _day;
};int main()
{Date d(2024,8,5);//实例化对象d.Print();return 0;
}
有关初始化列表:
1.初始化列表是每个成员变量定义初始化的地方,每个成员变量只能在列表中出现一次。
2.我们以前所写的构造函数,其实也走了初始化列表(对成员变量的定义),然后我们在函数体对成员变量赋值。
我们不显示写初始化列表,对于内置类型的成员变量不做处理,对于自定义类型的会去调用它的构造函数。
所以总的来说,我们写不写初始化列表,成员变量都会走初始化列表,因为这是成员变量定义的地方。
3.初始化列表会按照成员变量在类中的声明顺序进行初始化,跟成员在初始化列表出现的的先后顺序无关。建议声明顺序和初始化列表顺序保持一致。
class A{public:A(int a):_a1(a), _a2(_a1){}void Print() {cout << _a1 << " " << _a2 << endl;}private:int _a2;int _a1;};
这是一个声明顺序和初始化列表的顺序不一致导致的错误,_a2声明在前,初始化列表时先初始化_a2,用的是还初始化的_a1的值,所以_a2是个随机数。
4.C++11支持了在成员变量声明位置给缺省值,这个缺省值是没有显示在初始化列表初始化的成员用的。
class Date
{
public:Date(int year, int month, int day)//初始化列表:_year(year) ,_month(month),_day(day){}Date()//没有显示写初始化列表{}void Print(){cout << _year << "/" << _month << "/" << _day << endl;}private://年月日int _year = 2024; //给缺省值int _month = 5;int _day = 1;
};
5.引用成员变量,const成员变量,没有合适或者没有默认构造的类类型变量,必须在初始化列表位置进行初始化,否则会编译报错。
class C
{
public:C(int c):_c(c){}
private:int _c;
};class A
{
public:A(int a, int b ,int c):_a(a),_b(b),_c(c){}
private:int& _a;const int _b;C _c;
};
在对象实例化时,引用成员变量必须传递一个定义好的变量,如果是传递一个值的话,那类中的引用成员变量是用这个值构建的临时变量来初始化,构造函数结束后,临时对象销毁,类中的引用成员变量就是一个野引用。
被const修饰的变量只有一次初始机会,此后不能被修改,必须在初始化列表的定义。
2.隐式类型转换
C++支持内置类型隐式类型转换为类类型对象,需要有相关内置类型为参数的构造函数。
(单参数构造函数支持隐式类型转换)
class A
{
public:A(int a):_a(a){}
private:int _a;
};int main()
{//3构造⼀个A的临时对象,再⽤这个临时对象拷⻉构造aaA aa = 3;return 0;
}
值得一提的是,编译器遇到连续构造+拷贝构造,会直接优化成用3去构造aa出来。
- 如果是引用的隐式类型转换,需要加const,不加编译器会报错。
原因是2构造出来的临时对象具有常性,不能被修改,不加const会触发权限放大,编译器报错。
int main()
{const A& aa = 2;return 0;
}
如果我们不想隐式类型转换发生,那么可以在构造函数前加explicit。
class A
{
public:explicit A(int a) //不支持隐式类型转换了:_a(a){}
private:int _a;
};
在C++11后,支持了多参数构造。
class A
{
public:A(int a1, int a2) :_a1(a1),_a2(a2){}
private:int _a1;int _a2;
};int main()
{A aa = { 1, 2 };return 0;
}
3.static成员
static修饰的成员变量,是静态成员变量,同理也有静态成员函数。
有关静态成员变量:
- 静态成员变量一定要在类外进行初始化。
- 静态成员变量为所有类共享,不存在对象中,存放在静态区。
- 静态成员变量不能在声明位置给缺省值,因为它不走初始化列表。
有关静态成员函数:
- 静态成员函数没有this指针,所以访问不了类中的非静态成员,但可以访问静态成员。
非静态的成员函数可以访问任意的静态成员变量或者函数。
如果想在类外面访问静态成员,可以使用域作用限定符::或者点.来突破类域,从而访问,如:类名::静态成员或者对象.静态成员
静态成员也是类成员,也受到public、protected、private访问限定符的影响。
静态成员的应用:
统计创造出多少个类对象
class A
{
public:A(){++_count;}A(const A& a){++_count;}~A(){--_count;}static int GetCount(){return _count;}private:static int _count; //类里面声明
};int A::_count = 0; //类外面定义int main()
{A aa1;cout << A::GetCount() << endl;A aa2(aa1);cout << aa2.GetCount() << endl;return 0;
}
求1+2+3+...+n_牛客题霸_牛客网 (nowcoder.com)
class Sum
{
public:Sum(){_ret += _i;++_i;}static int GetRet(){return _ret;}
private: static int _i;static int _ret;};
int Sum::_i = 1;
int Sum::_ret = 0;class Solution {
public:int Sum_Solution(int n) {Sum* s = new Sum[n];delete[] s;s = nullptr;return Sum::GetRet();}
};
假设有A、B、C、D4个类的定义,程序中构造函数的调用顺序为:C A B D
析构函数的调用顺序为:B A D C
(先定义先构造,后定义的先析构,但是static改变了对象的生存作用域,会放在局部对象之后进行析构)
C c;int main(){A a;B b;static D d;return 0
;
}
4.友元与内部类
友元
友元是一种突破类访问限定符封装的方式,合适的使用可以提供便利,但友元会增加耦合度,破坏封装,谨慎使用。
- 关键字friend,并将其声明放到一个类里
- 友元分为:友元函数和友元类。
有关友元函数:
- 友元函数是一种声明,可以在类定义的任何地方,不受访问限定符影响。
- 外部友元函数可以访问类的私有和保护成员。
- 一个函数可以是多个类的友元函数。
- 友元函数不能被const修饰。(const是修饰成员函数的)
有关友元类:
- 友元类的成员函数都是另一个类的友元函数,可以访问另一个类的私有和保护成员。
- 友元类是单向的,如:A类是B类的友元,但反过来不是。
- 友元类的关系也不具有传递性,如:A是B的友元,B是C的友元,但A不是C的友元
class A
{friend void func(const A& a); //友元函数声明private:int _a = 1;
};void func(const A& a)
{cout << a._a << endl;
}int main()
{A aa;func(aa);return 0;
}
内部类
一个类定义在另一个类的内部,这个类就是内部类。
- 内部类是独立的类,只是受外部类和访问限定符限制,外部类定义的对象不包含内部类。
- 内部类默认是外部类的友元类。
- 内部类是一种封装方式,当A类专属B类使用时,可以将A类设计为B类的内部类。
class A
{
private:int _a = 5;static int _b;
public:class B{public:void Print(const A& a){cout << a._a << endl;cout << _b << endl;}};
};int A::_b = 10;int main()
{cout << sizeof(A) << endl;//4//A的大小不受内部类的影响,说明内部类是独立的类A aa;A::B bb;bb.Print(aa);return 0;
}
5.匿名对象
用类型定义出来的对象就是匿名对象。
class A
{
public:A(int a = 10):_a(a){}
private:int _a;
};int main()
{A aa(5); //有名对象A(5); //匿名对象return 0;
}
- 匿名对象生命周期只在当前一行,当你需要临时定义一个对象,就可以用匿名对象
- 匿名对象也具有常性。
const A& a1 = A(3); //必须加const,因为匿名对象也具有常性
拜拜,下期再见😏
摸鱼ing😴✨🎞