静态成员变量与函数
在代码中,有些类中我们希望知道它们调用了多少次构造函数和拷贝构造,比如下行代码
cpp
class A
{
public:
A() {
++count;
cout << "我构造了哦" << endl;
}
A(const A& a) {
++count;
cout << "我拷贝了哦" << endl;
}
~A() {}
private:
static int count;
};
int A::count = 0;
A func() {
A aa;
return aa;
}
int main() {
A aa;
func();
return 0;
}
结果:2次构造函数,0次拷贝构造 (其实不是,这里编译器进行了优化效果)
使用静态成员变量来监视的原因是:
用静态成员变量能够代表全部对象的count
不会像你来一个对象,我专门为你新搞个count出来,这样的话,就不会造成count的混乱,
到时候++的都是同一个count,方便登记。
在类里使用静态成员变量需要注意的是
静态成员变量定义和初始化要分离
类的静态成员变量定义和初始化要分离,它不走初始化列表,需要在类外给定义
原因很简单:静态成员变量只能初始化一次,如果你在类里面给缺省值,就相当于我每创建一个对象,就要对这个静态成员变量初始化一次,显然有违背于静态成员变量规则
类外访问count
如果我在外边想要访问count,情况又会如何?
cpp
cout << A::count << endl;
报错了,为什么呢?
注意,这里的A::的作用只是说明了count是属于类里的,并不代表说能在外面访问这个count,此种错误的情况无异于直接在类外写cout<<count<<endl
这种报错情况就是典型的在类外访问成员的私有变量,这是类的私有变量。
如果想要让它访问,以目前见解,有两种做法让它访问
1,让private变为公有
cpp
cout << A::count << endl;
cout << aa._a << endl;
我们将目光看向aa.count,这里的aa. 并不是说这个count是aa的,只是给出声明,这个aa是属于A的,让编译器去A类里面去寻找count,验证一下
三者访问的是同一个count地址,更能够说明count是属于全类,而不是单属于某个成员对象
就跟类里的成员函数一样,地址并不在成员函数里,只是告诉编译器,这个是属于这个类的
但我总不能一直公有吧,那我想获取值的话,就得用第二个方法
2,提供getCount函数
使其成为成员函数,达到只读不写的效果
cpp
int getCount() {
return count;
}
getCount函数所存在的问题
如果我不创建对象,我就调用不了这个getCount这个函数
可以给匿名对象,匿名对象方式A()
cpp
cout << A().getCount() << endl;
解决方案一:匿名对象
匿名对象的生命周期只有在这一行,意味着只有这一行能用,其他行就不能用了
解决方案二:使用静态成员函数
另一种方法是用静态成员函数,静态成员函数的特点是没有this指针
cpp
static int getCount() {
return count;
}
因为有this指针就必须要用对象调用
那现在如何用getCount函数呢
这跟public的情况下,调用A::count差不多的情况
此刻的新问题就是,这个静态的getCount还能不能访问类里面的其它成员变量
答案是不能了,因为没有this指针了
静态成员函数和静态成员变量,本质是受限制的全局变量和全局函数,它们专属这个类,受类域和访问限定符的限制
内置类型隐式转换成自定义类型
单一参数
cpp
namespace hl {
class B {
public:
B(int b)
:_b(b)
{}
int _b = 0;
};
}
void test2() {
hl::B bb1(1);
hl::B bb2(2);
hl::B bb3 = 3;
//先用3,构造一个匿名对象B(3),在拷贝构造给bb3
}
只要发生类型转换,就会产生临时变量
因为这个转换有一个int的单参数的构造函数支持的
如果不想让转换成功,构造函数可以加explicit,只防的住隐式转换,强转防不了
多参数
隐式类型转换也不一定是单参数,还可以是多参数的转换
例如:
cpp
class Date {
public:
Date(int year, int month = 1, int day = 1) {
_year = year;
_month = month;
_day = day;
}
private:
int _year, _month, _day;
};
void test3() {
Date d1 = (2024, 7, 19);
}
我们可以清楚的看到_year 被赋值成了19,而_month 和 _day都被赋值成了1
原因何有呢?
逗号运算
因为在执行 hl::Date d1 = (2024, 7, 19);
的时候,右边的(2024,7,19)会先进行逗号运算,没错,就是逗号运算,最后得到最后一个数字19
所以hl::Date d1 = (2024, 7, 19); 变成了 l::Date d1 = 19;
而另外两个参数有缺省值给它赋值,所以是两个1
解决方案不能用圆括号,要用花括号{}
友元补充:
友元函数会增添耦合度,即一处出问题,会影响很多处
还有友元类,友元是单向关系,意思就是我是你的朋友,你不一定是我的朋友
内部类:
概念
如果一个类定义在另一个类的内部 ,这个内部类就叫做内部类,内部类是一个独立的类,它不属于外部类,更不能通过外部类的对象去访问内部类的成员,外部类对内部类没有任何优越的访问权限
注意:内部类就是外部类的友元类,参见友元类的定义,内部类可以通过外部类的对象参数来访问外部类中的所有成员,但是外部类不是内部类的友元
特性:
1,内部类可以定义在外部类的public,protected,private都是可以的。
2,注意内部类可以直接访问外部类中的static成员,不需要外部类的对象/类名
3,sizeof(外部类) = 外部类,和内部类没有任何关系
cpp
namespace L {
class A {
private:
int _a;
public:
class B {
public:
int _b;
};
};
}
B就是一个普通类,只不过收到了A类的限制和访问限定符的限制,
B天生就是A的友元,A不是B的友元,即B是A的朋友,但A不是B的朋友,所以B能访问A,A不能访问B
编译器优化
前言
编译器的优化并不是全部编辑器都是一样的,不同的编译器有不同的优化效果,有小优,有大优,具体情况看大家的编译器。
两个已经存在的对象进行赋值,就叫做赋值拷贝
一个已经存在的对象初始化另一个要创建的对象,就是拷贝构造
cpp
namespace H {
class A {
private:
int _a;
public:
A(int a) {
cout << "我构造了哦" << endl;
}
A(const A& a) {
cout << "我拷贝构造了哦" << endl;
}
~A() {
cout << "析构一下" << endl;
}
};
}
void test5() {
H::A a1(1);
H::A a2(a1);
H::A a3 = a1;
}
正常的很
优化特点:在同一个表达式中 ,普遍会做
- 构造+构造 = 构造
- 构造+拷贝 = 构造
- 拷贝+拷贝 = 拷贝
H::A aa = 1;
按原本来说,1是还要进行一次隐式类型转换,先变成构造函数A类的,并且还要进行一次拷贝构造给aa,此次优化就是属于构造+拷贝 = 构造
这边没有拷贝构造,按原来的道理来说,他应该先构造A(2),紧接着在拷贝构造给aa,但是此处编译器为了省事,使用了优化,即构造 + 拷贝 = 构造
cpp
H::fun(H::A(2));
//相当于
H::fun(2);
我这边被狠狠的优化了,原本是 构造 + 拷贝构造 + 拷贝构造 的,现在直接变成构造,用了两次基本优化,这边都跨行优化了,非常狠
构造+构造 = 构造
构造+拷贝 = 构造
这样就只能仅仅在函数那里构造 + 拷贝 = 构造了 ,因为它们不在同一个地方
有些编译器根据检查上下文发现你这个变量没鸟用的时候,同样会直接把它优化掉
cpp
A a1(1);
A a2 = a1;
A a3 = a2;
例如这样,编译器会觉得a2根本没有产生的必要,就想着把它给干了,变成下面这样
一些构造时的优化,不同的编译器可能会不同
以上便是本次博文的学习内容,如有错误,还望各位大佬能够指正小生,谢谢观看!