C++中的类与对象(下)

上一节我们将类与对象中一个比较难的也是一个比较重要的模块学习了,在这节主要是一些细节上的补充。

文章目录

目录

前言

一、初始化列表

初始化列表的性质

初始化列表的总结

二、类型转换

C++中的类型转换

三、static成员

static的特点

一般情况下构造函数调用顺序:

一般情况下析构函数调用顺序:

四、友元

友元的性质:

五、内部类

内部类的用途:

六、匿名对象

匿名对象的使用场景:

匿名对象与临时对象的区别:

七、对象拷贝时的编译器优化

总结


前言

我们在上一节学习了几个编译器默认生成的几个函数,但是我们只是知道它们是编译器默认生成的以及它们大概是什么作用,在这节我们将进一步深入了解一下其中的奥秘。


一、初始化列表

我们在上一节学习了什么是构造函数,以及如何显示去实现它,我们一般是在构造函数函数体中通过赋值的方式进行初始化。除此之外,还有一种初始化的方式就是初始化列表。初始化列表的书写方式是,在构造函数名下面写上要进行初始化的一些成员变量,我们一般以一个:(冒号)开始接第一个要初始化的成员变量,我们将要初始化的值或表达式放在( )内传给那个变量即完成初始化了,后面的数据成员变量我们使用,(逗号)进行分割,这里我们需要注意的是在每个成员变量初始化完后不要顺手添上一个;(分号),那样就会发生报错,有时候我们可能还发现不了哪里发生报错了呢。

class Date
{
public:Date(int year = 1999, int month = 1, int day = 1):_year(year), _month(month), _day(day){//...}void print()const{cout << _year << "-" << _month << "-" << _day << endl;}private:int _year;int _month;int _day;
}

初始化列表的性质

1.每个成员变量只能在初始化列表中出现一次,在语法理解上就是初始化列表是每个成员变量进行初始化定义的地方。

2.有三种比较特殊的成员变量:引用类型成员变量,const修饰成员变量,没有默认构造函数的成员变量,必须在初始化列表中进行初始化,如果没有的话,编译器会发生报错。

3.C++11规定在成员变量声明处给缺省值,这个缺省值主要是给那些没有显示在初始化列表中初始化的成员变量使用的,同样地如果上面三种特殊的成员变量在成员变量声明处就给了缺省值的话,也可以不用在初始化列表中进行初始化。

4.能使用初始化列表进行初始化的成员变量就尽量使用,因为那些没有使用初始化列表进行初始化的成员变量在某种程度上编译器会让它们自己走初始化列表这条路。对于那些有缺省值的,初始化列表会用这些缺省值给它们进行初始化,对于那些没有缺省值的,如果是没有显示实现初始化列表的内置类型成员变量是否初始化取决于编译器,这个初始化值C++中并没有明确规定。如果是没有显示实现初始化列表的自定义类型成员变量则会调用这个成员变量的默认构造函数,如果没有默认构造函数就会发生报错。

5.初始化列表初始化的顺序是却决于成员变量在声明时的顺序,与在初始化列表中的顺序无关,这里是一个容易混淆的点,我们要记清楚。一般我们都将初始化列表的顺序与成员变量声明的顺序保持一致。

class Time
{
public:Time(int hour):_hour(hour){cout << "Time(int hour)" << endl;}
private:int _hour;
};class Date
{
public:Date(int& x, int year = 1999, int month = 1, int day = 1):_year(year), _month(month), _day(day), _t(12), ref(x), i(day){cout << "Date(int& x, int year = 1999, int month = 1, int day = 1)" << endl;}void print()const{cout << _year << "-" << _month << "-" << _day << endl;}private:int _year;int _month;int _day;int& ref;//引用类型的成员变量const int i;//const修饰的成员变量Time _t;  //没有默认构造函数的成员变量
};

初始化列表的总结

1.无论是否显示写初始化列表,每个构造函数都有初始化列表(在开始学习构造函数时我们没有提及,这里补充到)

2.无论成员变量是否在初始化列表中进行初始化,每个成员变量都会走初始化列表。

二、类型转换

我们在之前C语言中学习数据类型的时候,我们就知道了不同类型之间可以相互转换,隐式类型转换通常是高精度转换为低精度的数据类型,至于低精度的数据类型转换为高精度的数据类型,编译器一般无法通过隐式类型转换进行默认转换,因为从低精度转换为高精度会导致精度的缺失,可能会造成数据的不准确,但是有时为了某种需求时,需要将一种类型转换为另一种类型,就可以使用显示类型转换(强制类型转换)。

C++中的类型转换

1.在C++中同样支持隐式类型转换,对于那些内置类型的对象可以隐式类型转换为类类型的对象,但是需要有相应数据类型作为参数的构造函数。现在我们一般都是通过类来实例化一个对象来进行各种操作,对于那些简单的内置类型的对象有时候就不能够满足某些需求,于是我们就要使用我们的类类型对象,我们可以通过这个对象来调用不同类型的成员变量;

2.如果我们在构造函数前面加上一个关键字explicit时就不再支持隐式类型转换了;

3.类类型的对象之间也是支持类型转换的,但是需要相应的构造函数。

class A
{
public://1//explicit A(int a1) 我们加上explicit关键字后,就可以使其不能够进行隐式类型转换了A(int a1):_a1(a1){cout << "A(int a1)" << endl;}//2A(int a1, int a2):_a1(a1), _a2(a2){cout << "A(int a1, int a2)" << endl;}A(const A& a){cout << "A(const A& a)" << endl;}~A(){cout << "~A()" << endl;}void print()const{cout << _a1 << " " << _a2 << endl;}int Get()const{return _a1 + _a2;}private:int _a1 = 1;int _a2 = 2;
};class B
{
public:B(const A& a):_b(a.Get()){cout << "B(const A& a)" << endl;}
private:int _b = 1;
};int main()
{//1//这个对象实例化时调用的是第一个只有一个参数的构造函数,由于第一个构造函数中的参数是aa1于是它实例化传参初始化的是aa1A aa1 =5;//5是整型,会构造生成一个临时对象来存储5,然后再进行拷贝构造传给aa1,//因此上面的操作是构造+拷贝构造但是编译器默认优化成一步了直接构造,某种意义上是一种隐式类型转换aa1.print();//5 2(aa1按照传参给的值进行赋值,aa2编译器没有进行赋值于是就按照成员变量声明时的值进行初始化//由于const具有常性,所以我们要先生成一个临时对象来暂时保存一下由整型1类型转换生成的A类类型的临时变量const A& aa2 = 1;// C++11之后才⽀持多参数转化 //2A aa3 = { 2,2 };//这个对象实例化调用的是上面那个有两个参数的构造函数aa3.print();// aa3隐式类型转换为b对象 // 原理跟上⾯类似 //B类显示实现的构造函数里面的参数是一个const类型的,它对于普通的参数权限是更小的,// 因此我们可以将权限更大的(非const修饰)以及权限相等的(有const修饰的)传参给b,但是我们最终都要转换为const A&类型的B b = aa3;//引用也具有常性,我们也要通过临时对象去传参const B& rb = aa3;return 0;
}

上面的代码中,我们实现了两个构造函数,由于上面两个构造函数的参数个数不同,因此这两个构造函数构成重载,我们在main函数中直接将一个整型类型的对象赋值给A类类型的对象,两个对象的数据类型是不一样的,但是在A类中有一个构造函数中的参数是整型类型的,于是我们将一个整型类型的对象赋值过去后,编译器会自动转换为类类型的对象,具体转换的过程看上面的注释。上面代码中的aa3实例化时一次性传递了两个参数,在C++11之后编译器(我用的是VS2022)才支持多参数类型转化,同样地和第一个类型转换一样,它调用了A类中的那个有两个整型类型参数的构造函数来实现隐式类型转化。至于最后将A类类型的aa3隐式类型转化为B类类型对象,同样地是因为在B类中有一个A类类型作为参数的构造函数。

总的来说:在C++中如果想要实现内置类型隐式转化为类类型的,还是想要实现一个类类型对象转化为另一个类类型对象的话,还是得靠一个有相应参数的构造函数。(学到这里,得感叹一下前面学的构造函数还真是有用呢)

三、static成员

C++ 中的 static 关键字有多个用途,它能够影响变量和函数的生命周期、作用域以及如何在程序中共享数据。有时候我们定义一个变量,我们相要这个变量在全局中都可以使用,并非局部在某个函数或者某个类的时候,我们就可以考虑一下用 static 关键字来进行修饰。有时候我们在一个类中定义一个函数,我们想要突破类域来使用这个函数时,我们就可以用static 关键字,这也是一个好办法。

static的特点

1.用static 关键字修饰的成员变量被称为静态成员变量,我们需要在类外去初始化它;

2.静态成员变量被所有类对象所共用,它并不属于某个对象,尽管有时候我们在某个类中定义它,但是静态成员变量是放在静态区;

3.用static 关键字修饰的成员函数被称为静态成员函数,静态成员函数没有this指针,一般的成员函数都有一个隐藏的this指针参数;

4.静态成员函数可以访问其他静态成员变量,但是不可以访问非静态的,因为没有this指针;

5.非静态的成员函数,既可以访问静态成员变量也可以访问静态成员函数(有static修饰的成员变量/成员函数全局都可以使用的);

6.突破类域可以访问静态成员,可以通过类名::静态成员 或者 对象名.静态成员 来访问静态成员变量或者静态成员函数;

7.如果静态成员变量也是类的成员,那么也会受到public,private,protected等访问限定符的限制;

8.静态成员变量不能够在成员变量声明的地方给缺省值,因为在调用构造函数时会进行初始化列表进行初始化,如果初始化列表显示实现了就使用初始化列表进行初始化了,如果没显示实现那么就会使用缺省值进行初始化列表初始化,但是静态成员变量并不属于哪个类,也不走初始化列表初始化。

#include<iostream>
using namespace std;
class A
{
public:
//构造函数A(){++_scount;cout << "A()" << endl;}
//拷贝构造函数A(const A& t){++_scount;cout << "A(const A& t)" << endl;}
//析构函数~A(){--_scount;cout << "~A()" << endl;}
//静态成员函数static int GetACount(){return _scount;}
private:// 类⾥⾯声明 //静态成员变量,注意我们不能在静态成员变量声明处给缺省值,也不能在类中进行初始化,必须在类外进行初始化static int _scount;
};// 类外⾯初始化 
int A::_scount = 0;int main()
{cout << A::GetACount() << endl;//直接调用A类中的成员函数,由于只有A的构造与析构,没有拷贝构造,因此_scount不变A a1, a2;//实例化两个对象变量,因此自动调用了两次构造函数A a3(a1);//这里运用了一次拷贝构造函数//以下两种是突破类域的访问方式:1.类名::  2.对象名.cout << A::GetACount() << endl;cout << a1.GetACount() << endl;//至于析构函数是在最后进行调用的(函数销毁后,进行一些内存的清理时调用的)// 编译报错:error C2248: “A::_scount”: ⽆法访问 private 成员(在“A”类中声明) //cout << A::_scount << endl;发生报错是因为静态成员变量也受到public,private,protected等修饰词的限制return 0;
}

上面的代码中我们设置了一个静态成员变量,我们是在类中定义声明静态成员变量的,但是我们一定要在类外对静态成员变量进行初始化,我们定义的静态成员变量在全局都可以使用,不论是在类中还是在函数体中都是可以调用的,后面我们又在类中定义了一个静态成员函数,我们同样可以突破类域进行访问调用,但是要使用上面两种方式进行调用。

这里我再来说一下不同类型对象调用构造函数和析构函数的顺序。对于构造函数,首先定义在全局域的对象首先调用构造函数,因为在main函数外的对象都是在main栈帧创建前生成的,在main中调用构造函数的顺序就是定义对象的顺序,先定义的先调用构造函数,对于有静态成员类型的也是按照定义顺序来调用构造函数的。其实在 main 函数之前,程序加载时,静态成员会被初始化。即使它在 main 中没有被显式调用,它会在程序启动(运行到那一行代码)时就被构造。我们可以用下面那个代码进行验证一下。对于析构函数,在全局域中定义的对象只会在代码完全结束时才会销毁调用析构函数,至于main函数中的普通类对象的析构函数调用顺序是后定义的先调用析构函数,至于那些静态成员也是按照逆序的顺序来调用析构函数的,不过是在程序退出销毁时才调用。

class MyClass {
public:MyClass() {std::cout << "MyClass constructor called!" << std::endl;}
};class StaticClass
{
public:static MyClass staticObj;StaticClass() {std::cout << "StaticClass constructor called!" << std::endl;}
};MyClass StaticClass::staticObj; // 静态成员初始化int main() 
{MyClass regularObj; // 创建普通对象std::cout << endl;StaticClass obj; // 创建静态类的对象return 0;
}

一般情况下构造函数调用顺序:

  1. 静态和全局变量的构造顺序:程序启动时,根据在文件中出现的顺序(如果涉及多个文件,顺序不确定)。
  2. 局部变量的构造顺序:根据它们在函数中的声明顺序。
  3. 成员变量的构造顺序:根据它们在类中的声明顺序。
  4. 继承中的构造顺序:基类构造函数先调用,派生类构造函数后调用。
  5. 静态成员的构造顺序:静态成员变量按照声明顺序构造。

一般情况下析构函数调用顺序:

  1. 局部对象

    • 局部对象的析构函数在它们的作用域结束时调用。例如,函数中的局部对象在该函数执行完后会自动销毁,其析构函数会在该时刻被调用。
  2. 全局/静态对象

    • 全局对象和静态对象会在程序结束时销毁,且它们的析构顺序通常是在程序退出时按照逆序来调用的。

static 修饰的静态类:

对于静态类而言,它们有几种情况需要区分:

  1. 静态局部变量

    • 如果某个类的实例是 static 关键字修饰的局部变量,它将在第一次调用时创建,并且在程序的退出阶段销毁。它的生命周期从首次执行到程序退出的整个过程。
    • 它的析构函数会在程序退出时被调用,在静态变量析构时,系统会自动按逆序销毁所有静态局部对象。
  2. 全局静态变量

    • 如果某个对象是全局的 static 变量,程序退出时析构函数也会在逆序进行调用,但它的生命周期从程序开始执行到程序结束。

四、友元

在C++中,友元(friend) 是一种特殊的机制,它允许一个非成员函数或其他类访问另一个类的私有成员(private)和保护成员(protected)。友元的作用是打破类的封装性,使得某些特定的函数或类能够直接访问类内部的数据成员和成员函数。

友元的性质:

1.友元提供了一种突破类访问限定符封装的方式,友元分为:友元函数和友元类,在函数声明或者类声明的前面加friend,并且把友元声明放在一个类的里面;

2.外部友元函数可以访问类的私有和保护成员,友元函数仅仅是一种函数声明,它不是类的成员函数;

3.友元函数可以在类的任何地方进行声明,不受到类访问限定符的限制;

4.在一个类中可以有多个友元函数;

5.友元类中的成员函数可以是另一个类的成员函数,都可以访问另一个类中的私有和保护成员;

6.友元类的关系是单向的,如果A类是B类的友元类,但是B类不是A类的友元类;

7.友元类的关系是不具有传递性的,如果A类是B类的友元类,B类是C类的友元类,但是A类不是C类的友元类;

8.友元类虽然有时候能够带来便利,但是友元会增加耦合度,破坏了封装,所以友元不能够多用。

#include<iostream>
using namespace std;
// 前置声明,都则A的友元函数声明编译器不认识B 
class B;
//进行一个类的声明,和我们之前写函数的声明放在头文件里面是一样的,在一个文件中的代码,不同的位置不同的声明位置也是不一样的,
//放在后面的函数,前面的函数如果要用,要事先声明一下
class A
{// 友元声明 (可以在任何位置进行声明,可以在中间,可以在前面可以在后面,不过我们一般都放在前面声明,是一个好的书写习惯)friend void func(const A& aa, const B& bb);
private:int _a1 = 1;int _a2 = 2;
};
class B
{// 友元声明 friend void func(const A& aa, const B& bb);
private:int _b1 = 3;int _b2 = 4;
};//这个函数是上面两个类的友元函数,上面两个类中都有该函数的友元函数声明
//友元函数可以在类外进行访问私有制成员变量
void func(const A& aa, const B& bb)
{cout << aa._a1 << endl;cout << bb._b1 << endl;
}
int main()
{A aa;B bb;func(aa, bb);//1 3return 0;
}

#include<iostream>
using namespace std;
class A
{// 友元声明 (友元类,将B类使用friend修饰,于是B类就变成了A类的友元类,于是B类就可以访问A类中的私有成员变量)//但是我们要注意:A类不是B类的友元类,在A类中不能访问B类中的私有制成员变量。友元类是单向的,友元类是不可以传递的friend class B;//下面这个函数会发生报错,因为在A类中无法访问B类的成员变量//void func3(const A& aa)//{//	cout << aa._a1 << endl;//1//	cout << _b1 << endl;//3//}private:int _a1 = 1;int _a2 = 2;
};//在上面已经进行了友元类的声明,于是下面在B类中就可以访问A类中的成员变量
class B
{
public:void func1(const A& aa){cout << aa._a1 << endl;//1cout << _b1 << endl;//3}void func2(const A& aa){cout << aa._a2 << endl;//2cout << _b2 << endl;//4}
private:int _b1 = 3;int _b2 = 4;
};
int main()
{A aa;B bb;bb.func1(aa);//1 3cout << endl;bb.func2(aa);//2 4//cout << endl;//aa.fun3(bb);return 0;
}

上面的两组代码,我们可以清楚地看到友元函数/友元类的作用,我们将友元函数定义在类外面(友元函数可以定义在类内,也可以定义在类外),然后在A类B类中都进行了友元函数的声明,于是就可以突破类域访问A类B类中的成员变量了;对于友元类,我们要首先明确一下哪个类是哪个类友元类,因为友元类只具备单向性不具备双向性,后面的作用与友元函数的功能是一样的,可以访问调用另一个类中的成员变量或成员函数,也不受访问限定符的限制。

五、内部类

在C++中,内部类(Nested Class) 是指定义在另一个类(外部类)内部的类。也就是说,内部类是作为外部类的一部分来定义的,它可以直接访问外部类的私有成员(具体是否能够访问也受权限控制),但外部类不能直接访问内部类的成员,除非通过合适的对象。

内部类默认是外部类的友元类。内部类的本质也是一种封装,当A类更B类紧密关联,A类的出现主要是给B类使用,那么就可以考虑将A类作为B类的内部类,如果将其放到private/protected位置,那么A类就是B类的专属内部类,其他地方也就用不了了。


#include<iostream>
using namespace std;
class A
{
private:static int _k;int _h = 1;
public://B类定义在A类中,于是我们就将B类叫做A类的内部类,我们会一般默认B就是A的友元类,B中的成员函数可以调用A类中的成员变量//内部类是一个独立的类,不过是一个类定义在另一个类中。如果放在private/protected中就是那个类的私有类class B // B默认就是A的友元 {public:void foo(const A& a){cout << _k << endl; //OKcout << a._h << endl; //OK}};
};
int A::_k = 1;
int main()
{cout << sizeof(A) << endl;//计算类A的大小,注意:计算类大小的方式与计算结构体的方式是一样的,它们都是要进行内存对齐的方式进行计算的//由于在A类中只有两个成员变量,还有一个B类,B类中只有成员函数。我们计算类大小时,只计算成员变量的大小,不计算成员函数的大小,// 而且静态成员变量是放在静态区的,不是在类中的,因此也不计算在类的大小中A::B b;//实例化一个对象变量,由于我们实例化的是一个B类对象,但是B类是放在A类中的,于是我们想要实例化调用时,要通过A类进行访问A aa;//实例化一个A类对象b.foo(aa);//使用B类中的一个对象变量来调用B类中的成员函数,由于B类是在A类中定义的,因此它可以调用A类中的私有制成员变量作为参数return 0;
}

上面的代码中我们将B类设置为A类的内部类,于是我们计算了一下A类的大小,这时我们将B类作为A类的内部类了,那么B类的大小同样计算为A类的大小中,我们计算类大小只看成员变量的大小不看成员函数的大小,而且静态成员变量是不属于类中对象的,隐藏A类中可以计算大小的就是一个整型变量_h。后面两个我们A类来调用B类来实例化对象,同样地,B类中的成员函数也可以使用A类中的成员变量。

内部类的用途:

  • 封装性:如果某些类只在外部类的范围内有用,可以将它们作为内部类封装起来。
  • 避免命名冲突:在复杂的项目中,内部类有助于避免与外部类同名的类产生冲突。
  • 模块化设计:可以通过内部类来组织和分离不同的功能。

总结来说,C++中的内部类提供了一种结构化的方式来组织类的成员,它能够增加类的封装性和可读性,同时允许在需要时访问外部类的成员。

六、匿名对象

在 C++ 中,匿名对象(Anonymous Objects)指的是没有名字的对象,相比我们之前定义的 类型 对象名(实参)定义出来的叫做有名对象。它们通常在表达式中直接创建并使用,而不需要显式地为其指定一个变量名。匿名对象在创建时会立即进行操作或传递,生命周期通常是非常短的。


class A
{
public:A(int a = 0):_a(a){cout << "A(int a)" << endl;}~A(){cout << "~A()" << endl;}
private:int _a;
};class Solution
{
public:int Sum_Solution(int n){//...return n;}
};
int main()
{//匿名对象与有名对象,不仅是定义的方式不一样,而且两者定义对象的生命周期也是不一样的,匿名对象的生命周期很短,在调用构造函数后紧跟着就调用了析构函数//至于有名对象,在我们实例化对象时,编译器自动调用构造函数,在最后进行销毁空间清理内存时才会调用析构函数,而且调用的顺序是后定义的先调用A aa1;cout << endl;// 不能这么定义对象,因为编译器⽆法识别下⾯是⼀个函数声明,还是对象定义 //A aa1();// 但是我们可以这么定义匿名对象,匿名对象的特点不⽤取名字, // 但是他的⽣命周期只有这⼀⾏,我们可以看到下⼀⾏他就会⾃动调⽤析构函数 //定义匿名对象:类名(),在()内可以传参数A();cout << endl;A(1);cout << endl;A aa2(2);//这个是直接构造有名对象,这个与上面第一个是一样的,由于这么我们显示实现的构造函数里面有一个缺省参数,所以我们写不写参数都是可以的cout << endl;// 匿名对象在这样场景下就很好⽤,当然还有⼀些其他使⽤场景,这个我们以后遇到了再说 cout << Solution().Sum_Solution(100) << endl;return 0;
}

上面代码中,我们将两种实例化对象的方式都展示出来了,我们可以看到有名对象实例化出来后在程序结束时才会调用析构函数,但是匿名函数在调用完构造函数后紧跟着就调用析构函数了,可见匿名对象的生命周期如此之短。我们使用匿名对象有个好处,正如上面代码所示,如果我们想要调用输出类中某个函数的值时,我们可以直接使用匿名对象.函数,不用像有名对象那样,先实例化一个对象,然后再通过这个对象来调用类中的函数,如果要实例化的对象很多,是不是挺麻烦的。所以匿名对象有优点也有缺点,看你如何去使用它了。

匿名对象的使用场景:

  • 函数返回值:当函数返回一个对象时,可以返回一个匿名对象。
  • 临时对象:在某些表达式中,临时创建一个对象并立即使用它,比如在函数调用中传递一个对象。
  • 链式调用:匿名对象在链式调用中常见,用于连接多个操作。

匿名对象与临时对象的区别:

  • 临时对象:是由某些表达式(例如返回值、类型转换等)隐式创建的对象,并在一段时间后销毁。
  • 匿名对象:是没有名字的对象,通常用于创建临时对象,在表达式中直接使用。

七、对象拷贝时的编译器优化

我们在进行一些对象构造或者拷贝的时候,有时候编译器会默认将某些步骤跳过或者说优化掉,随着现在的编译器不断更新迭代,其优化的程度也是越来越大,这里我们稍微了解一下其中优化的地方。

#include<iostream>
using namespace std;
class A
{
public:A(int a = 0):_a1(a){cout << "A(int a)" << endl;}A(const A& aa):_a1(aa._a1){cout << "A(const A& aa)" << endl;}A& operator=(const A& aa){cout << "A& operator=(const A& aa)" << endl;if (this != &aa){_a1 = aa._a1;}return *this;}~A(){cout << "~A()" << endl;}
private:int _a1 = 1;
};void f1(A aa)
{}A f2()
{A aa;return aa;
}
int main()
{// 传值传参 A aa1;f1(aa1);cout << endl;// 隐式类型,连续构造+拷⻉构造->优化为直接构造 f1(1);// ⼀个表达式中,连续构造+拷⻉构造->优化为⼀个构造 //先匿名构造一个对象,然后再构造一个临时对象,然后再拷贝构造,最终都直接优化成一个构造了f1(A(2));cout << endl;cout << "***********************************************" << endl;// 传值返回 // 返回时⼀个表达式中,连续拷⻉构造+拷⻉构造->优化⼀个拷⻉构造 (vs2019 debug) // ⼀些编译器会优化得更厉害,进⾏跨⾏合并优化,直接变为构造。(vs2022 debug) f2();cout << endl;// 返回时⼀个表达式中,连续拷⻉构造+拷⻉构造->优化⼀个拷⻉构造 (vs2019 debug) // ⼀些编译器会优化得更厉害,进⾏跨⾏合并优化,直接变为构造。(vs2022 debug) A aa2 = f2();//直接跳过构造实例化aa,构造生成aa2,然后再拷贝构造,在vs2022中优化成直接构造生成一个aa2cout << endl;// ⼀个表达式中,连续拷⻉构造+赋值重载->⽆法优化 aa1 = f2();//直接构造aa,然后赋值运算符重载cout << endl;return 0;
}

从上面的代码以及运行结果,我们可以看出,编译器并非是像我们想象的那样运行的,它有时会将某几个步骤直接优化为一个步骤,有时直接跳过一个步骤进入下一步,总之编译器的优化是为了避免那些重复的操作,我们在之后如果遇到这种由编译器优化后的运行结果我们要学会通过网上一些资料,以及自己的一些试验去看看,编译器优化了哪些地方,然后再对照代码去看懂运行结果。


总结

这里,我们就将C++中的类与对象初步学习完了,我们现在是大概了解了一下类与对象,后面的水还很深,我们不能松懈下来,要继续努力~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/894298.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Versal - 基础4(VD100+Versal IBERT)

1. 简介 在之前的一篇博文中&#xff0c;我分享了在 Zynq Ultrascale MPSoC 中使用 IBERT 的方法。 《Vivado - 集成眼图分析仪 Serial I/O IBERT 误码率_vivado ibert-CSDN博客》 本文进一步探讨 Versal 中使用 IBERT 的方法。 2. 硬件平台 芯片&#xff1a;XCVE2302-SF…

《HelloGitHub》第 106 期

兴趣是最好的老师&#xff0c;HelloGitHub 让你对编程感兴趣&#xff01; 简介 HelloGitHub 分享 GitHub 上有趣、入门级的开源项目。 github.com/521xueweihan/HelloGitHub 这里有实战项目、入门教程、黑科技、开源书籍、大厂开源项目等&#xff0c;涵盖多种编程语言 Python、…

英语语法 第一天

I’m a student. 我是个学生 我是个新东方的学生 I’m a student of New Oriental School 我爱你 I love you 我在心中爱你 I love you in my heart. 这是一朵花 This is a flower 这是一朵在公园里的花 This is a flower in the park.(修饰部分在修饰词后面) 主干…

“新月之智”智能战术头盔系统(CITHS)

新月人物传记&#xff1a;人物传记之新月篇-CSDN博客 相关文章链接&#xff08;更新&#xff09;&#xff1a; 星际战争模拟系统&#xff1a;新月的编程之道-CSDN博客 新月智能护甲系统CMIA--未来战场的守护者-CSDN博客 目录 一、引言 二、智能头盔控制系统概述 三、系统架…

猿人学web 19题(js逆向)

这题直接点击翻页抓包&#xff0c;然后获取seesion ID请求即可 求和代码 import requestssession requests.Session() cookies {sessionid:eao9i00r8pt4xu6uzzx2k01ttqn51yc9} urlhttps://match.yuanrenxue.cn/api/match/19?page sum0 for i in range(1,6):response sess…

c语言:编译和链接(详解)

前言 要将编译和链接&#xff0c;就不得不提及编译器是如何运作的&#xff0c;虽然这部分知识是针对于要创造编译器和创作语言的人所需要清楚的&#xff0c;但作为c语言的学习者也需要了解一下&#xff0c;修炼内功&#xff0c;尤其是对于想学习c的人而言。 编译器的运作过程…

积分和微分的区别

积分&#xff1a; 积分是由微小量求大量&#xff0c;由微观的数据求得整体的状况。运算是对总量求和。 微分&#xff1a; 微分是由大量求微小量&#xff0c;反应微观的状况&#xff0c;运算是伴随着求导。 峰值检测电路: 上图检测的误差主要来自与二极管的正向导通电压降&am…

OVS-DPDK

dpdk介绍及应用 DPDK介绍 DPDK&#xff08;Data Plane Development Kit&#xff09;是一组快速处理数据包的开发平台及接口。有intel主导开发&#xff0c;主要基于Linux系统&#xff0c;用于快速数据包处理的函 数库与驱动集合&#xff0c;可以极大提高数据处理性能和吞吐量&…

亚博microros小车-原生ubuntu支持系列:18 Cartographer建图

Cartographer简介 Cartographer是Google开源的一个ROS系统支持的2D和3D SLAM&#xff08;simultaneous localization and mapping&#xff09;库。基于图优化&#xff08;多线程后端优化、cere构建的problem优化&#xff09;的方法建图算法。可以结合来自多个传感器&#xff0…

安卓(android)实现注册界面【Android移动开发基础案例教程(第2版)黑马程序员】

一、实验目的&#xff08;如果代码有错漏&#xff0c;可查看源码&#xff09; 1.掌握LinearLayout、RelativeLayout、FrameLayout等布局的综合使用。 2.掌握ImageView、TextView、EditText、CheckBox、Button、RadioGroup、RadioButton、ListView、RecyclerView等控件在项目中的…

爬虫基础(四)线程 和 进程 及相关知识点

目录 一、线程和进程 &#xff08;1&#xff09;进程 &#xff08;2&#xff09;线程 &#xff08;3&#xff09;区别 二、串行、并发、并行 &#xff08;1&#xff09;串行 &#xff08;2&#xff09;并行 &#xff08;3&#xff09;并发 三、爬虫中的线程和进程 &am…

自签证书的dockerfile中from命令无法拉取镜像而docker的pull命令能拉取镜像

问题现象&#xff1a; docker pull images拉取镜像正常 dockerfile中的from命令拉取镜像就会报出证书错误。报错信息如下&#xff1a; [bjxtbwj-kvm-test-jenkins-6-243 ceshi_dockerfile]$ docker build . [] Building 0.4s (3/3) FINISHED …

计算机网络 IP 网络层 2 (重置版)

IP的简介&#xff1a; IP 地址是互联网协议地址&#xff08;Internet Protocol Address&#xff09;的简称&#xff0c;是分配给连接到互联网的设备的唯一标识符&#xff0c;用于在网络中定位和通信。 IP编制的历史阶段&#xff1a; 1&#xff0c;分类的IP地址&#xff1a; …

面对企业文件交换难题,镭速跨网文件交换系统是如何解决的?

在当今这个数字化快速发展的时代&#xff0c;企业越来越依赖于数据交换来维持其业务运作。无论是内部网络之间的沟通还是与外部合作伙伴的数据共享&#xff0c;高效且安全的跨网文件交换都显得尤为重要。然而&#xff0c;在实际操作中&#xff0c;许多企业面临着各种各样的挑战…

Many Whelps! Handle It! (10 player) Many Whelps! Handle It! (25 player)

http://db.nfuwow.com/80/?achievement4403 http://db.nfuwow.com/80/?achievement4406 最少扣你50DKP! 第二阶段 当奥妮克希亚升空后&#xff0c;在10秒内引出50只奥妮克希亚雏龙&#xff0c;随后击败奥妮克希亚。 World of Warcraft [CLASSIC][80猎人][Grandel][最少扣你5…

自制虚拟机(C/C++)(一、分析语法和easyx运用,完整虚拟机实现)

网上对虚拟机的解释很多&#xff0c;其实本质就一句话 虚拟机就是机器语言解释器 我们今天要实现汇编语言解释器&#xff0c;下一次再加上ndisasm反汇编器就是真正虚拟机了 注:这里的虚拟机指的是VMware一类的&#xff0c;而不是JVM&#xff0c;python一样的高级语言解释器 …

36. printf

1. printf 格式化函数说的是 printf、 sprintf 和 scanf 这样的函数&#xff0c;分为格式化输入和格式化输出两类函数。学习 C 语言的时候常常通过 printf 函数在屏幕上显示字符串&#xff0c;通过 scanf 函数从键盘获取输入。这样就有了输入和输出了&#xff0c;实现了最基本…

实验八 JSP访问数据库

实验八 JSP访问数据库 目的&#xff1a; 1、熟悉JDBC的数据库访问模式。 2、掌握使用My SQL数据库的使用 实验要求&#xff1a; 1、通过JDBC访问mysql数据&#xff0c;实现增删改查功能的实现 2、要求提交实验报告&#xff0c;将代码和实验结果页面截图放入报告中 实验过程&a…

python学opencv|读取图像(四十六)使用cv2.bitwise_or()函数实现图像按位或运算

【0】基础定义 按位与运算&#xff1a;全1取1&#xff0c;其余取0。按位或运算&#xff1a;全0取0&#xff0c;其余取1。 【1】引言 前序学习进程中&#xff0c;已经对图像按位与计算进行了详细探究&#xff0c;相关文章链接如下&#xff1a; python学opencv|读取图像&…

使用vhd虚拟磁盘安装两个win10系统

使用vhd虚拟磁盘安装两个win10系统 前言vhd虚拟磁盘技术简介准备工具开始动手实践1.winX选择磁盘管理2.选择“操作”--“创建VHD”3.自定义一个位置&#xff0c;输入虚拟磁盘大小4.右键初始化磁盘5.选择GPT分区表格式6.右键新建简单卷7.给卷起个名字&#xff0c;用于区分8.打开…