目录
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的临时对象,再⽤这个临时对象拷⻉构造aa
A 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😴✨🎞