目录
初始化列表
初始化列表的特点
类型转换、explicit
隐式类型转换
explicit关键字
static成员
静态成员变量
静态成员函数
友元
友元函数
友元类
内部类
匿名对象
编译器优化
初始化列表
初始化列表就是类成员初始化的地方
函数有它声明和定义的地方,变量也有,类成员也有
先来看看日期类的默认构造函数
class Date
{
public:
Date(int year = 1900, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1(2024, 7, 15);
return 0;
}
在类里面显示出来的成员我们把它叫做声明
如果你认为我们就这样把_year,_month,_day给初始化了,那就错了
这并不是初始化,这是赋值
如果我们要初始化类成员的话应该这样做
class Date
{
public:
Date(int year = 1900, int month = 1, int day = 1)
:_year(year)
,_month(month)
,_day(day)
{}
private:
int _year;
int _month;
int _day;
};
这一块就是初始化列表
先是一个冒号开始,然后以逗号分隔,成员变量后面跟初始值或者表达式
这样我们就完成了一个成员的定义和初始化
那么这两种写法有什么区别呢?
这两种写法给我们带来的效果都是一样的,但是无论怎么写我们的成员变量都要经过初始化列表一遍,就算没有写初始化列表也会!所以这里建议尽量使用初始化列表初始化
如果我们不知道初始化列表怎么给,我们可以在声明的地方给一个缺省值,例如:
class Date
{
public:
Date(int year = 1900, int month = 1, int day = 1)
:_year(year)
,_month(month)
,_day(day)
{}
private:
int _year = 1900;
int _month = 1;
int _day = 1;
};
这样就算我们没有写初始化列表也会给我们自动初始化上这些值
注意:
const成员变量,带引用的成员变量,只有一次初始化的机会!那就是在初始化列表中!
class A
{
public:
A(int a = 1)
{
_a = a;
}
private:
const int _a;
};
因为const常量不能被赋值,只能在初始化的地方被初始化,正确代码如下:
class A
{
public:
A(int a = 1)
:_a(a)
{}
private:
const int _a;
};
class A
{
public:
A(int a = 1)
:_a(a)
{}
private:
int& _a;
};
初始化列表的特点
如果我们没有在声明的地方给缺省值,也没有写初始化列表,那么值由编译器决定
如果我们给了缺省值没有写初始化列表,那么会根据缺省值初始化
如果我们即给了缺省值也给了初始化列表,那么根据初始化列表的值初始化
注:上述行为于构造函数内部行为无关
初始化列表中按照成员在类中声明的定义来初始化,于初始化列表中出现的先后顺序无关
例如:
class A
{
public:
A()
:_a(1)
, _b(_a)
{}
//private:
int _b;
int _a;
};
int main()
{
A a;
cout << a._a << endl;
cout << a._b << endl;
return 0;
}
输出结果:
我们可以看到_a的值为1,_b的值为随机值s
这是因为类声明是先声明的_b,才声明的_a,所以初始化列表会先初始化_b,再初始化_a,而不是因为_a在初始化列表中初始化就初始化_a
类型转换、explicit
隐式类型转换
什么是隐式类型转换?通过下面的例子我想你就明白了
int main()
{
double d = 3.14;
int i = d;
cout << i << endl;
return 0;
}
double类型的d为什么能赋值给int类型的i?
在 i = d 的时候这里会构造出一个临时对象,d会先构造给这个临时变量,然后临时变量才会将值拷贝构造给i
回到正题
class A
{
public:
A(int a)
:_a(a)
{}
private:
int _a;
};
int main()
{
A a = 1;
return 0;
}
这里将1拷贝构造给A类型a就是隐式类型转换,将int类型转换成A类型
explicit关键字
如果我们想要编译器再严格一点,只能同类型转换,那么我们可以在构造类型前面加上关键字explicit
class A
{
public:
explicit A(int a)
:_a(a)
{}
private:
int _a;
};
int main()
{
A a = 1;
return 0;
}
此时我们就运行不了我们的代码了
static成员
静态成员变量
用static修饰的成员变量,称为静态成员变量,静态成员变量必须要在类外初始化!
它的作用和C语言中一样,不属于某个具体的对象,存放在静态区中,生命周期跟全局变量一致
类内初始化:
class A
{
public:
A(int a)
:_a(a)
{}
void Print()
{
cout << _a << endl;
}
private:
static int _a;
};
int main()
{
A a;
a.Print();
return 0;
}
类外初始化:
class A
{
public:
//A(int a)
// :_a(a)
//{}
void Print()
{
cout << _a << endl;
}
private:
static int _a;
};
int A::_a = 5;
int main()
{
A a;
a.Print();
return 0;
}
静态成员函数也收public、private、protected访问限定符约束
class A
{
public:
//A(int a = 1)
// :_a(a)
//{}
void Print()
{
cout << _a << endl;
}
private:
static int _a;
};
int A::_a = 5;
int main()
{
A a;
cout << A::_a << endl;
return 0;
}
静态成员函数
用static修饰的成员函数,称之为静态成员函数,静态成员函数没有this指针!
静态成员函数中可以访问静态成员,但是不能访问成员函数,因为没有this指针
class A
{
public:
A(int a = 1)
:_a(a)
{}
static void Print()
{
cout << _a << endl;
}
private:
int _a;
};
非静态成员函数可以访问任意的静态成员变量和静态成员函数(毕竟是全局的)
友元
友元分为友元函数和友元类
友元函数
class A
{
public:
A(int a = 1)
:_a(a)
{}
private:
int _a;
};
void Print(const A& a)
{
cout << a._a << endl;
}
正常这样Print函数是无法访问到A类里面的private成员变量的,但是如果它是A类的朋友就可以了
class A
{
friend void Print(const A& a);
public:
A(int a = 1)
:_a(a)
{}
private:
int _a;
};
void Print(const A& a)
{
cout << a._a << endl;
}
友元函数仅仅是一种声明,他不是类的成员函数
把函数的第一行写一遍,然后前面加上friend,这样Print就是A的朋友了,这样就不会报错了
可以看出,外部友元函数是可以访问类中的私有成员的,当然保护和共有也不例外
友元类
class A
{
public:
A(int a = 1)
:_a(a)
{}
void Print()
{
B b;
cout << b._b << endl;
}
private:
int _a;
};
class B
{
friend class A;
public:
B(int b = 0)
:_b(b)
{}
private:
int _b;
};
正常来说A类中的Print函数是不能访问b中private的_b
但是这时候,B声明了A是我的朋友,那么A中就可以访问B类中的私有了
但是要注意:友元类关系不能传递
B说A是我的朋友,但是不代表A的朋友是B
class A
{
public:
A(int a = 1)
:_a(a)
{}
void Print()
{
B b;
cout << b._b << endl;
}
private:
int _a;
};
class B
{
friend class A;
public:
B(int b = 0)
:_b(b)
{}
void Print()
{
A a;
cout << a._a << endl;
}
private:
int _b;
};
A是B的友元,但是B可不是A的友元!
友元和面向对象封装的思想有些相反了,破坏了这种封装
友元会增加耦合度,但是破坏了封装,所以不宜多用
内部类
如果一个类定义在另一个类的内部,那么这个类叫做内部类
class A
{
public:
A(int a = 1)
:_a(a)
{}
class B
{
public:
void func(const A& a)
{
cout << a._a << endl;
}
};
private:
int _a;
};
int main()
{
A a;
A::B b;
b.func(a);
return 0;
}
这里的B类就是定义在A类中,此时B就是A的友元,可以访问A类中的私有
A类的实现就是专门为B准备的
匿名对象
先来看个代码
class A {
public:
int func(int n) {
//...
return n;
}
};
int main()
{
A a;
a.func(1);
return 0;
}
如果要调用func函数我们可以先定义一个A类对象,然后再使用func
那还有什么更好的调用方式吗?
我们可以使用匿名对象来调用这个func函数
class A {
public:
int func(int n) {
//...
return n;
}
};
int main()
{
A().func(1);
return 0;
}
这里的A()就是一个匿名对象,它的生命周期只有这一行,这一行之后就会结束,所以目的也就只是想调用func函数不那么麻烦且少一点空间
编译器优化
class A {
public:
A(int a = 1)
:_a(a)
{
cout << "A(int a)" << endl;
}
A(const A& a)
:_a(a._a)
{
cout << "A(const A& a)" << endl;
}
~A()
{
cout << "~A()" << endl;
}
private:
int _a;
};
int main()
{
A a1(10);
A a2 = a1;
return 0;
}
明明有两个成员变量a1、a2,怎么才调用一次构造函数呢?
这里就是由于编译器的优化,由于A a2 = a1这个表达式做了两件事情,一件就是构造,另一件就是拷贝构造,所以编译器优化成了只有拷贝构造即可
越是新版本的编译器优化的会越狠
将Debug模式换成Release也会有更多的优化
这些都是为了加快代码运行的效率
完