网站入口你会回来感谢我的,哈尔滨大型网站开发,短视频运营岗位职责,wordpress打开过慢前言#xff1a;
本篇内容为前面的补充#xff0c;介绍了我们使用类时需要注意些什么以及一些编译器的优化#xff0c;可能有些理解不到位或者错误#xff0c;请斧正。
目录
前言#xff1a;
1.再谈构造函数
2.#xff08;c98#xff09;隐式类型转换中的编译器的优… 前言
本篇内容为前面的补充介绍了我们使用类时需要注意些什么以及一些编译器的优化可能有些理解不到位或者错误请斧正。
目录
前言
1.再谈构造函数
2.c98隐式类型转换中的编译器的优化
3.explicit关键字
4.static成员
5.匿名对象
6.友元函数
7.内部类
8.编译器的一些场上的优化
总结
若有歧义请指出感谢阅读 1.再谈构造函数
我们在构造函数体中给成员变量赋值能叫做成员变量的初始化吗并不可以这种行为只是给成员变量赋初值在函数体中我们可以多次赋值而初始化只能初始化一次。
那该如何初始化呢
使用初始化列表。以一个冒号开始接着是一个以逗号分割的数据成员列表每个成员变量的后面跟一个放在括号中的初始值表达式。
class Date
{
public:Date(int year, int month, int day):_year(year),_month(month),_day(day){}private:int _year;int _month;int _day;
};那像const这样的必须在定义位置的初始化的放在成员变量里面该怎么初始化呢
可以给缺省值但不是初始化
class A
{
public:A():_x(1){_a1;}private:int _a1 1;int _a2 2;const int _x;//可以给缺省值但不是初始化};另外缺省值也会在初始化列表进行初始化 如上图我们可以看到 _a1的结果是2虽然没有在初始化列表中显示的初始化但是还是会走初始化列表初始化其次_a2的结果是0虽然_a2的缺省值是2但是在初始化列表中显示的初始化为了1所以再--就是0。而对于即不给缺省值也不给初始化的普通成员变量经过测试那就是随机值。 对于成员变量是引用的与成员变量是自定义类型的
class B
{
public:B(int b):_b(0){cout B() endl;}private:int _b;
};class A
{
public:A():_x(1),_ref(_a1),_bb(0){_a1;}private:int _a1 1;int _a2 2;int _ref;B _bb;const int _x;//可以给缺省值但不是初始化};成员变量是引用的跟const一样本身引用就是要在定义的位置初始化所以我们可以给缺省值或者要在初始化列表初始化。
对于自定义类型的成员变量_bb会去调用它的构造函数初始化吗经过我的测试_bb这个自定义类型的成员变量如果不在A中的初始化列表初始化就要去调用它的构造函数但是一定要确保B中的构造函数一定是默认的也就是说必须是全缺省的或者是不写编译器自动生成的。而上面的代码中B中的构造函数不是默认的构造函数所以我们如果在A的类中不对_bb进行初始化列表的初始化就会报错。 再看一个例子 注释部分的构造函数可以对两个自定义类型的成员进行了初始化列表的初始化而内置类型由于没有显示的写就使用了缺省值所以可以如果Stack这个自定义类型中的构造函数是默认的什么也没写的构造函数也是可以的对于自定义类型初始化去调用它的默认构造函数内置类型的初始化由于没有显示的写就使用它的缺省值。 其次还需要注意一个点
class A1
{
public:A1(int a):_a1(a),_a2(_a1){}void Print(){cout _a1 _a2 endl;}private:int _a2;//声明的次序就是在初始化列表中的初始化的顺序int _a1;
};int main(){//A aa;A1 aa(1);aa.Print();return 0;
}
上面的结果应该是什么结果是输出1和随机值。
这是因为成员变量在类中的声明次序就是其在初始化列表中的初始化顺序与其在初始化列表中的先后次序无关。 总结
首先不管在初始化列表中显示的写没写初始化都会在初始化的列表中走一遍。
其次记住一个原则给初始化就在初始化列表中给初始化。 2.c98隐式类型转换中的编译器的优化
class A
{
public:A(int a): _a1(a){cout A(int a) endl;}A(const A aa):_a1(aa._a1){cout A(const A aa) endl;}private:int _a1;
};int main(){A aa1(1);//构造函数A aa2 1;//隐式类型转换return 0;
}
根据我们之前的隐式类型转换的知识我们可以知道这里对aa2这个对象赋值就是在进行隐式类型转换首先1先构造一个临时对象这个临时对象再拷贝给aa2所以会去调用拷贝构造注意拷贝构造也是构造所以也有初始化列表。
但是我们看到结果不是这样的 为什么呢这就是编译器所做的优化因为编译器觉得自定义类型的这种初始化写起来还要调用拷贝构造所以直接就优化了直接就优化为了一步构造1直接构造aa2。注意这里的优化只限定于c98的单参数的构造 而对于这一种编译器还能这样优化吗答案是不能的因为我们知道10先构造出一个A类型的临时变量而这个临时变量又具有常性且ref是这个临时变量的别名所以需要加上const而就是由于这个临时变量具有常性编译器在这里就不会优化掉这个临时变量所以10就没法直接构造ref了而是先构造这个临时变量这里使用的vs2022的编译器发现也没有调用拷贝构造可能是编译器做的更极端了也优化了但是我们知道其后的原来即可。 3.explicit关键字 explicit的引入就是为了防止隐式类型转换这里加上了explicit A aa21和const A ref10的隐式类型转换就没有了就编不过了。 但是上面的隐式类型转换都是对于单参数的构造对于多参数的构造c11可以使用多参数的构造来进行隐式类型转换 其实也都是先调用构造然后再进行隐式类型转换再经过编译器的优化 会节省一次隐式类型转换产生的拷贝。
同样的如果不想使用隐式类型转换就在构造函数上加上explicit防止构造函数的隐式类型转换这时A aa(1,1)这样需要隐式类型转换的就编不过了。
如果想使用隐式类型转换让编译器进行优化节省一次拷贝就可以不加explicit。 4.static成员
我们将声明为static的类成员称为类的静态成员用static修饰的成员变量称为静态成员变量用static修饰的成员函数称之为静态成员函数。注意的是静态成员函数没有this指针静态成员变量在类中声明在类外进行初始化。 统计程序中创建出了多少个类对象
class A
{
public:A(int a 0){cout 构造 endl;}A(const A aa){cout 拷贝构造 endl;count;}static int GetCount()//没有this指针{return count;//static函数没有this指针访问不到成员count只能读不能写}private:static int count;//声明属于所有对象属于整个类};int A::count 0;//定义初始化
void func(A a)
{}int main()
{A aa1;//调用构造A aa2(aa1);//调用拷贝构造func(aa1);//函数传参且参数是A类型的所以调用拷贝构造A aa3 1;//隐式类型转换经过编译器的优化优化掉了拷贝构造只有构造A aa4[10];//调用10次构造cout aa3.GetCount() endl;//如果GetCount是个static修饰的只能接受返回的count不能改//类中的静态成员也受访问限定符的限制如果我们不让静态成员count设为私有如何访问count//A::count;//aa2.count或者aa3.count count属于所有对象//A* ptrnullptr;ptr-count 这里不会去解引用会直接去静态区中找return 0;
} 首先上面代码需要注意的是GetCount是一个静态成员函数所以访问它可以通过对象访问即aa3.GetCount或者指定内域访问A::GetCount但是由于静态成员函数没有this指针所以函数内不能访问非静态成员变量这里返回count就是因为count是个静态成员变量换成是普通的变量就不行了 其次类中的静态成员也是受类的访问限定符的限制的像这里count为私有虽然是静态的全局的变量但是在类外面还是访问不到的。
如果我们不将count设为私有那怎么访问这个静态成员变量呢
1.A::count 直接指定内域访问
2.aa2.count或者aa3.count 因为static成员是属于所有对象的所以可以
3.A* ptrnullptrptr-count 这里不会解引用直接去静态区找 同时我们上方的代码也复习之前的知识 分析结果
前两个分别调用构造与拷贝构造没什么说的第三个是函数传参由于参数也是A也就是类类型的所以会调用拷贝构造然后是下面的隐式类型转换编译器优化掉了拷贝直接就是构造然后可以看到如果自定义对象是数组可以看到调用了10次。 总结 5.匿名对象
当我们需要调用某个类的成员函数时需要先创建一个对象所以我们引入了一个匿名对象可以直接不创建对象直接去调用写法为类名 通过析构函数可知匿名对象的周期只在它出现的这一行到下一行就会销毁。 返回值也可以使用匿名对象更加简洁。 6.友元函数
友元函数在我们之前提到过现在再来细看一下。 7.内部类
内部类c很少用隔壁Java常用。 首先如果B这个类在A中是公有的可以直接在外面指明内域访问例如A::B bb
其次如果B是私有的那就不能通过A来访问了所以B这个内部类既受A的类域的
限制因为B为公有在外面需要指定在A的内域又受到A的访问限定符的限制
然后B这个内部类天生就是A的友元所以可以通过内部类访问外部类的成员静态的也可以
补充一个类里面公有可以访问私有 总结 8.编译器的一些场上的优化
首先先来分析3个优化
class A
{
public:A(int a 0){cout 构造 endl;}A(const A a){cout 拷贝构造 endl;}~A(){cout ~A endl;}
};void func1(A aa)
{}void func2(A aa)
{}A func3()
{return A();
}int main()
{A aa1 1;func1(aa1);func1(2);func1(A(3));return 0;
}
第一个就是隐式类型转换编译器会将隐式类型转换产生的拷贝优化掉所以就只有一个构造。
第二个是传参调用拷贝构造但是这个拷贝构造不会被优化大部分情况下只有c98中的那个单参数构造和c11的多参数构造会优化但是可以使用传引用传参来减少拷贝。由于拷贝构造产生了临时变量所以在func1函数结束时会调用析构销毁这个临时变量。
第三个与第四个也都是传参调用拷贝构造但是结果发现拷贝构造被优化为了构造理想的结果不应该是拷贝构造然后跟析构吗当时我也疑惑了好久其实这是因为编译器的处理在我的vs2022的编译器下可能由于编译器太新优化的比较极端编译器看到你func1函数什么也没写干嘛要在拷贝构造一次干脆直接就优化为了拿形参构造实参可以看到构造后紧跟的就是析构这也表明还是存在拷贝产生的临时变量需要销毁。
最后主函数结束aa1销毁调用析构。 另外如果我们使用func2的传引用传参后两个传参会直接报错因为它们是临时变量使用引用后传过去后函数作用域销毁这个变量就找不到了而aa1的作用域在main函数中尽管fun2的函数结束但是还是能找到aa1。 再看一种优化 在这个场景下 A aa先构造返回aa再调用拷贝构造因为没有创造对象所以这个拷贝构造有可能被优化掉拷贝的临时变量销毁调用一次析构aa这个局部对象销毁再调用一次析构那这两个析构谁先调用的呢拷贝构造后面经跟的就是拷贝时创建的临时变量销毁调用的析构
此时还没有优化但是当我们来接受它的返回值时 aa拷贝给一个临时变量临时变量再拷贝给给aa1这里就会被优化为一个拷贝构造。 如果我们在将赋值重载再写出来下面的称为赋值接受 就会得到下面的结果 如果先定义一个对象再接收返回值就是aa2先构造func3里aa构造返回时一个拷贝构造这个拷贝构造有可能会被优化因为没有创造新的对象来接收返回值看编译器这里就是被优化了然后没有被优化那会多出来一对拷贝构造析构。结果中的两个析构分别是func3函数结束aa销毁调用的析构和main函数结束aa2销毁的析构。 如果是下面的场景下面的称为拷贝构造接受 如果直接调用fun2返回的匿名对象调用一次构造匿名对象出了fun2析构一次。
如果接收fun2的返回值匿名对象A()先构造一次返回时拷贝构造一次返回给aa2再拷贝一次编译器会优化用 匿名对象直接构造aa2直接就是构造一次所以最后一次析构是aa2销毁的析构 总结
优化的场景很多我们只要记住 总结
若有歧义请指出感谢阅读