当前位置: 首页 > news >正文

【c++】类和对象:让你明白“面向一个对象有多重要”:构造函数,析构函数,拷贝构造函数的深入学习

 

 

文章目录

  • 什么是面向对象?
  • 一:类是什么?
  •         1.类的访问限定符
  •         2.封装
  •         3.类的实例化
  •         4.this指针
  • 二:类的6个默认成员函数
    •  1.构造函数
    •   2.析构函数
    •   3.拷贝构造函数

 


什么是面向对象?

c语言是面向过程的,关注的是过程,分析出求解问题的步骤,通过函数调用逐步解决问题。

比如洗衣服:

7e24e3d1122544df93b2155af8b31cc6.png

c++是基于面向对象的,关注的是对象,将一件事情拆分成不同的对象,靠对象之间的交互完成。

f8786edf1e2547dc9e55ff0c4b2ea865.png

 在C语言中很多的过程在c++中被分为了人 衣服 洗衣机 洗衣粉,想要完成洗衣粉这件事只需要人将衣服放进洗衣机,倒入洗衣粉,启动洗衣机就完成了。

 


 

一、类是什么?

C语言结构体中只能定义变量,在c++中结构体内不仅可以定义变量,也可以定义函数。比如:之前我们用C语言方式实现的栈,结构体中只能定义变量,现在以c++的方式实现,会发现struct中也可以定义函数。

struct Stack
{void Init(int n = 4){a = (int*)malloc(sizeof(int) * n);if (nullptr == a){perror("malloc申请空间失败");return;}capcity = n;top = 0;}void Push(int x){}void Pop(){}int Top(){}bool Empty(){}int* a;int capcity;int top;
};
int main()
{Stack st;st.Init();st.Push(1);st.Push(2);st.Push(3);return 0;
}

 就像上面的代码段一样,以前C语言是不支持将函数写入结构体的,而c++现在能做到了。

而上面的struct在c++中更喜欢用class来代替。

那么怎么创建一个类呢?看下面代码段:

class classname    // class后面跟你自己想要取的类名
{//类体,由成员函数和成员变量组成
};  //后面一定要加;和结构体一样

class为定义类的关键字,classname为类的名字,{}中为类的主体,注意类定义结束时后面分号不能省略。类体中的内容称为类的成员,类中的变量称为类的属性或成员变量,类中的函数称为类的方法或者成员函数。类中定义的变量都可以直接在类中使用,类外则需要域限定符。

类的两种定义方式:

1.声明和定义全部放在类体中,需要注意的是,成员函数如果在类中定义,编译器可能会当成内联函数来处理。比如下面这样的:

class classname
{
public:void add(){year++;}
private :int year;
};

2.类声明放在头文件中,成员函数的定义放在.cpp文件中。

class classname
{
public:void add();
private :int year;
};
void classname::add()
{year++;
}

 类的访问限定符:

c++中有三种访问限定符,分为public(公有),private(私有)私有的在类外不可以访问,protected(受保护的)同样在类外不可以访问。

c++实现封装的方式:用类将对象的属性和方法结合在一块,让对象更加完善,通过访问权限选择性的将其接口提供给外部的用户使用。

1.pubic修饰的成员在类外可以直接被访问

2.protected和private修饰的成员在类外不能直接被访问

3.访问权限作用域从该访问限定符出现的位置开始直到下一个访问限定符出现时为止。

4.如果后面没有访问限定符,作用域到  }  及类结束。

5.class的默认访问权限为private,struct的默认访问权限为public(因为要兼容C语言)

注意:访问限定符只在编译时有用,当数据映射到内存后,没有任何访问限定符上的区别。

class Date
{
public:void Init(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 d1;d1.Init(2023,2,5);d1.Print();return 0;
}

 大家觉得上面这个代码段可以成功打印出年月日吗?答案是不可以,因为我们在private中定义的年与Init函数传来的参数year一样,这就导致编译器识别不出来,所以我们在定义成员变量的时候最好都像month那样在前面加个符号用来区分。

32e7e5f34a8a49a484ed193b2db866d9.png

封装 :

面向对象的三大特性:封装,继承,多态。

封装的意思就是将数据和操作数据的方法进行有机结合,隐藏对象的属性和实现细节,仅对外公开接口来和对象进行交互。封装的本质就是一种管理,让用户更方便的使用类。举个例子:就像电脑的主机一样,主机只提供开机键等接口,而实际上电脑真正工作的东西是cpu等硬件,而这些硬件是不会暴露在外边让用户看到的。

类的实例化:

用类的类型创建对象的过程,称为类的实例化。类是对对象进行描述的,是一个模型一样的东西,限定了类有哪些成员,定义出一个类并没有分配实际的内存空间来存储它,比如以下代码:

9142a051f33b488ea5d30fdb9622338b.png

3bfc7a22044244c68ce520b7306edb8b.png

 我们已经说过类是对对象进行描述的,只有创建了一个类对象,才会给这个类对象分配空间,这样就可以使用类里面的函数等变量。下图为正确的使用方式:

66e88c3feae44dfcb143875fad6d6a5c.png

下面我们来看类中成员如何存储的:

class Date
{
public:void Init(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 d1;cout << sizeof(d1) << endl;return 0;
}

 大家可以猜一猜d1这个对象的大小是多少?答案是12,12不就是private中三个成员变量的大小吗,为什么成员函数不占用空间呢?

90d90eb9c48e47fba9ba910838ab7f05.png

大家看上图,d1的year变量和d2的year变量是在同一块空间吗?答案是不在同一块空间,因为对象实例化后会给每个对象都开辟一个空间,那么d1的year肯定是在d1这个对象开辟的空间内,d2的year是在d2这个对象开辟的空间内。

e3949817191144409ee4326ab28b5fee.png 那么 d1的init函数和d2的init函数是在同一块空间吗?答案是是的,c++中为了防止每个对象都开辟空间存储不同的函数所以将函数放在了公共的代码段,想要调用这个函数直接去公共的代码段去找即可,这也就解释了为什么我们再计算对象的大小的时候不包含函数的大小了。至于为什么成员变量不设为公共的问题就很好回答了,应该每个对象都能对自己的成员变量进行修改,如果设为一个公共的那么d2对象修改year的值也会将d1对象的year进行修改。

// 类中既有成员变量,又有成员函数
class A1 {
public:void f1() {}
private:int _a;
};// 类中仅有成员函数
class A2 {
public:void f2() {}
};// 类中什么都没有---空类
class A3
{};

上面这三个类的sizeof大小是多少呢?

有了上面的解释回答这道题就很容易了,首先A1中只有变量_a占实际空间,所以大小为4字节。

A2中只有成员函数,而成员函数在代码段中那么这个A2就相当于A3是一个空类,空类在c++中占一个字节。

this指针

class Date
{
public:void Init(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 d1;Date d2;d1.Init(1, 2, 3);d2.Init(4, 5, 6);d1.Print();d2.Print();return 0;
}

 对于上面的代码段,有这样一个问题:Date类中有Init和Print两个成员函数,函数体中没有关于不同对象的区分,那当d1调用Init函数时,该函数是如何知道设置d1对象,而不是设置d2对象呢?

对于这个问题,c++中引用了this指针来解决这个问题。即:c++编译器给每个"非静态的成员函数"增加了一个隐藏的指针参数,让该指针指向当前对象(函数运行时调用该函数的对象),在函数体中所有"成员变量"的操作,都是通过该指针去访问。只不过所有的操作对用户是透明的,即用户不需用来传递,编译器自动完成。比如下面代码:

class Date
{
public:void Init(int year, int month, int day)//用户看到的//实际上  void Init(Date* this,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 d1;Date d2;d1.Init(1, 2, 3);//用户看到的//实际上//d1.Init(&d1, 1, 2, 3);d2.Init(4, 5, 6);d1.Print();d2.Print();return 0;
}

 在这里要注意,我们不能显式的自己去调用的时候传入对象的地址,这样编译器会报错。

那么this指针有什么作用呢?

class Date
{
public:void Init(int year, int month, int day){this->year = year;this->month = month;this->day = day;cout << this << endl;}void Print(){cout << year << "年" << month << "月" << day << "日" << endl;}
private:int year;   ///这些只是声明并不是定义int month;int day;
};int main()
{Date d1;Date d2;d1.Init(1, 2, 3);d2.Init(4, 5, 6);d1.Print();d2.Print();return 0;
}

 a07e4f26d5b245c49e78128fc9de4db0.png

 之前Init函数中不能分辨的year等变量用上this指针就可以正确分辨,还可以打印此对象的地址

那么this指针存放在哪里呢?this指针存放在栈中,因为this是隐含形参/vs下面是存在ecx寄存器中

this指针可以为空吗?看以下代码:

class Date
{
public:void Init(int year, int month, int day){this->year = year;this->month = month;this->day = day;cout << this << endl;}void Print(){cout << year << "年" << month << "月" << day << "日" << endl;}void Func(){cout << "Func()" << endl;}
private:int year;   ///这些只是声明并不是定义int month;int day;
};int main()
{Date* ptr = nullptr;ptr->Func();return 0;
}

 上面这段代码可以正常编译吗?很多人看到ptr是个空指针然后去调用Func函数会觉得这里对空指针进行解引用了,这样理解其实是不对的,首先这个代码可以正常编译看下图:

aa0e9e7973a149db8b6bf1f3ae7451d8.png

39ef39a4e26d4b1090b22333d8db7f9e.png

 

这里解释一下为什么可以编译,我们之前说过调用类中的函数时编译器会隐式修改为传对象的地址然后函数多了一个this指针的参数,所以当我们调用func这个函数的时候,编译器通过this指针找到了类中的这个函数即使把ptr这个空指针传给了this,也是可以正常使用的。那么下面这个程序的运行结果又是怎么样的?

af4a0aa973324b2e94ef0d978b09b93a.png

9e74f41a112d46a9847785e5c681631e.png  

上图这段代码运行起来程序崩溃了,首先这个Init和刚刚的func函数一样都不在对象里面,他们都在公共区域,调用这个函数直接跳到存放代码的地址,这些都没问题,有问题的是ptr是空指针,ptr给this传了个空指针然后再Init函数中这个空指针指向Year这个变量,这就是对空指针进行解引用了。

f9d09b2483414813a2ebcee3d10c7b8f.png

 那么上图中这个代码是否可以正常运行呢?很多人看到括号内对ptr空指针进行解引用了以为程序会崩溃,但其实并不是,编译器还是先去对象里找有没有Func()这个函数,然后编译器发现找不到通过this指针找到了Func函数的公共代码段,而这里的(ptr)是起到了给传给this指针的作用。

e7eef19f0d6a49259667a7da6acb4200.png

那么上图中的这个代码运行起来会不会崩溃呢? 这个一定是崩溃了,编译器先去找year是不是在对象里,找到后发现这个对象有自己的空间所以对空指针进行解引用了。通过上面几个问题大家应该知道this指针是可以为空的了。

二、类的默认6个成员函数

如果一个类中什么成员都没有,简称为空类。空类中真的什么都没有吗?并不是,任何类在什么都不写时,编译器会自动生成以下6个默认成员函数,默认成员函数:用户没有显式实现,编译器会生成的函数称为默认成员函数。

构造函数主要完成初始化工作

析构函数主要完成清理工作

拷贝构造是使用同类对象初始化创建对象

赋值重载主要是把一个对象赋值给另一个对象

完成取地址重载的两个函数很少会自己实现所以就不在讲解

构造函数

构造函数是一个特殊的成员函数,名字与类名相同,创建类类型对象时由编译器自动调用,以保证每个数据成员都有一个合适的初始值,并且在对象整个生命周期内只调用一次

构造函数的特性:1.函数名与类名相同     

                             2.无返回值

                             3.对象实例化时编译器自动调用对应的构造函数

                             4.构造函数可以重载

class Stack
{
public:Stack(int capcity = 0,int top = 0){_capcity = capcity;_top = top;}void Print(){cout << "_capcity:" << _capcity << endl;cout << "_top:" << _top << endl;}
private:int* a;int _capcity;int _top;
};
int main()
{Stack st;Stack st1(10, 4);st.Print();st1.Print();return 0;
}

f35fe87e9d074af8a700ff4adec17819.png

 

无参构造函数和带参构造函数的调用方式是不一样的,上面的代码我们在构造函数中用了缺省值的方式,必须要说明的是无参构造函数直接Stack st;即可,也就是说对象实例化创建后就会调用,而带参的构造函数必须在后面加括号写入参数入上图中st1一样。

需要注意的是:

class Date
{
public:Date(){_year = 1;_month = 1;_day = 1;}Date(int year = 1, int month = 1, int day = 1){_year = year;_month = month;_day = day;}void Print(){cout << _year << "年" << _month << "月" << _day << "日" << endl;}
public:int _year;   int _month;int _day;
};int main()
{Date d1;d1.Print();return 0;
}

903e386c2e2c4bee867608e9b24e1867.png

像上图中这样的情况,语法本身并没有问题,是因为编译器无法分清无参的构造函数和带缺省参数的构造函数,解决的方法为调用是以带参构造函数的方式去调用。

无参的构造函数和全缺省的构造函数都称为默认构造函数,并且默认构造函数只能有一个。
注意:无参构造函数、全缺省构造函数、我们没写编译器默认生成的构造函数,都可以认为
是默认构造函数  (原因是会产生歧义)

f443ae4606724be6ab578b257a026c18.png

我们之前已经说过,在实例化对象的时候必须调用构造函数,如果我们不写编译器会给一个默认的构造函数,那么我们就看看默认的构造函数可以完成对象的初始化吗

class Date
{
public:void Print(){cout << _year << "年" << _month << "月" << _day << "日" << endl;}
public:int _year;   int _month;int _day;
};int main()
{Date d1;d1.Print();return 0;
}

 可以看到我们是没有写构造函数的。

8ee9900860e746b992e6a0403693b05b.png

当我们调用的时候发现好像编译器默认的构造函数好像并不能完成初始化,这是怎么回事呢?这是因为c++把类型分为内置类型(基本类型)和自定义类型。内置类型就是语言提供的数据类型,如int/char...等,自定义类型就是我们使用class/struct/union等自己定义的类型,而编译器生成的默认构造函数会对自定义类型成员调用它的默认成员函数。

也就是说默认生成的构造函数,对内置类型成员不做处理。自定义类型成员会去调用它的默认构造(不用传参数的构造) 

class MyQueue
{
public://默认生成的构造函数对自定义类型,会调用它的默认构造函数void Push(int x){}Stack _pushST;Stack _popST;
};int main()
{MyQueue mq;return 0;
}

 26701c21c48e43bb8d6beed92c2c2d63.png

从上图中我们发现默认构造函数对于自定义类型会进行初始化 。析构函数作为专门初始化的函数大多人都认为应该将内置类型也初始化了而不是只针对自定义类型,针对这个问题在c++11中打入了一个补丁,就是可以直接给内置类型变量一个缺省值,什么意思呢?看下面代码:

class Date
{
public:void Print(){cout << _year << "年" << _month << "月" << _day << "日" << endl;}
public:int _year = 1;   int _month = 1;int _day = 1;
};int main()
{Date d1;d1.Print();return 0;
}

94b1e5c9b4b34afabf40ca6fc9b641f9.png

通过这个补丁就能解决内置类型初始化的问题了。 

析构函数

与构造函数功能相反,析构函数不是完成对对象本身的销毁,局部对象销毁工作是由编译器完成的,而对象在销毁时会自动调用析构函数,完成对象中资源的清理工作。

析构函数的特性:1.析构函数是在类名前加上~

                             2.无参数无返回值类型

                             3.一个类只能有一个析构函数,若未显式定义,系统会自动生成默认的析构函                                 数。注意:析构函数不能重载。

                             4.对象生命周期结束时,c++编译系统系统自动调用析构函数

class Stack
{
public:Stack(int capcity = 0,int top = 0){_capcity = capcity;_top = top;}void Print(){cout << "_capcity:" << _capcity << endl;cout << "_top:" << _top << endl;}~Stack(){cout << "这是析构函数" << endl;}
private:int* a;int _capcity;int _top;
};
int main()
{Stack st;Stack st1(10, 4);st.Print();st1.Print();return 0;
}

a413c6fd54f545fca8d0d14e9e04f1dd.png                                 

ba037a2d360c4ce0a61f6a57045cc3d3.png

f48755ea9e0849138f802b0e327120ac.png

我们在return处打了一个断点然后F5跳到断点处F11进入函数发现进入到了析构函数中并且打印了

 默认生成的析构函数与默认生成的构造函数一样,对内置类型成员不做处理。自定义类型成员会去调用它的默认析构函数。析构函数的调用遵循后进先出原则,也就是说后定义的对象先进行析构函数的调用。

class Time
{
public:Time(){cout << "Time()" << endl;}~Time(){cout << "~Time()" << endl;}
private:int _hour;int _minute;int _second;
};
class Date
{
private:// 基本类型(内置类型)int _year = 1970;int _month = 1;int _day = 1;// 自定义类型Time _t;
};
int main()
{Date d;return 0;
}

c42e5f7b828c4106aad556c87ce6c123.png  

在main中根本没有创建Time类的对象,但还是打印了Time和~Time,可见析构函数确实会对自定义类型调用其自己的析构函数。(构造函数同理)

拷贝构造函数

只有单个形参,该形参是本类类对象的引用(一般常用const修饰),在用已存在的类类型对象创建新对象时由编译器自动调用。

1.拷贝构造函数是构造函数的一个重载形式  以下是拷贝构造函数实例:

class Date
{
public:Date(int year = 1, int month = 1, int day = 1){_year = year;_month = month;_day = day;}Date(const Date& d){_year = d._year;_month = d._month;_day = d._day;}void Print(){cout << _year << "年" << _month << "月" << _day << "日" << endl;}
public:int _year ;   int _month ;int _day ;
};int main()
{Date d1;Date d2(d1);   //调用拷贝构造函数d1.Print();d2.Print();return 0;
}

de295a47636b45b4ab93f29f55fc43a4.png 我们可以看到d2确实和d1的参数是一样的说明拷贝成功了。

const修饰能起到什么作用呢?我们都知道加const修饰就是为了不被修改,看以下代码:

d0a0ba7c253c441ca7f84949b858a5dc.png

这里加const其实是为了防止将谁是谁的拷贝的位置写错了,一旦写错不加const那么用来拷贝的那个类里面的东西都被修改了。

当然用const修饰还有第二个好处,那就是防止权限的放大。

1b8c7efd0c5341f5870e9bed043e9a62.png 就如图中我们定义了一个const的对象,再调用拷贝构造时发现报错了,原因是d1本来是const this*,到了拷贝构造函数中变成了this*权限放大了,想要解决在拷贝构造的函数参数中加上const即可。

34101a1bfd9f4d2881004f421e25e347.png 

2.拷贝构造函数的参数只有一个且必须是类类型对象的引用,使用传值方式编译器直接报错,因为会引发无穷递归调用。

为什么会引发无穷递归调用呢,那是因为我们在传值传参的时候只要是自定义类型那么编译器会自动调用拷贝构造函数,举个例子:Date(Date d)如果这是拷贝构造函数,在调用这个函数的时候发现参数为自定义的Date类型那么就会调用拷贝构造函数又出现了Date(Date d)然后一直无穷递归下去,而使用引用为什么会避免呢?因为引用就是他本身再调用也是他本身不会再去调用新的拷贝构造函数。那么为什么编译器不能去拷贝自定义类型呢?

e749057019524a239e60d0ad1cd74d24.png

想要解决这样的问题就直接在拷贝构造函数中重新给要拷贝的对象开和被拷贝对象同样的空间,这样他们两个的地址不同在释放内存的时候就不会崩溃了。 

3.日期类不需要写拷贝构造,因为内置类型编译器也会处理。

class Date
{
public:Date(int year = 10, int month = 10, int day = 10){_year = year;_month = month;_day = day;}void print(){cout << _year << "年" << _month << "月" << _day << "日" << endl;}
private:int _year;int _month;int _day;
};
int main()
{Date d1;Date d2(d1);d2.print();return 0;
}

ab3aa27bfecf4fe0958b9d09c982c275.png

通过上面的代码我们可以发现即使我们不写拷贝构造函数编译器也能完成日期类的拷贝,那么自定义类型可以吗?

typedef int DataType;
class Stack
{
public:Stack(size_t capacity = 10){cout << "Stack(size_t capacity = 10)" << endl;_array = (DataType*)malloc(capacity * sizeof(DataType));if (nullptr == _array){perror("malloc申请空间失败");exit(-1);}_size = 0;_capacity = capacity;}void Push(const DataType& data){// CheckCapacity();_array[_size] = data;_size++;}~Stack(){cout << "~Stack()" << endl;if (_array){free(_array);_array = nullptr;_capacity = 0;_size = 0;}}
private:DataType *_array;size_t    _size;size_t    _capacity;
};
int main()
{Stack s1;Stack s2(s1);return 0;
}

d00e4791d04d472b8d4825f76fe18be1.png 

 我们以栈为例,运行后发现程序崩溃了,这是因为编译器默认的拷贝方式是浅拷贝这里我们在上面已经讲过这个拿图来看:

2453f1c0fc9a433c8a02c3b272a72ddb.png

3645992392784a3f9bb6efd8b28a397d.png 我们调试发现确实两个栈的地址是一样的,所以在析构函数释放空间的时候两个对象释放两次导致崩溃了。解决方式如下:

typedef int DataType;
class Stack
{
public:Stack(size_t capacity = 10){cout << "Stack(size_t capacity = 10)" << endl;_array = (DataType*)malloc(capacity * sizeof(DataType));if (nullptr == _array){perror("malloc申请空间失败");exit(-1);}_size = 0;_capacity = capacity;}void Push(const DataType& data){// CheckCapacity();_array[_size] = data;_size++;}Stack(const Stack& s){_array = (DataType*)malloc(sizeof(DataType) * s._capacity);if (_array == nullptr){perror("malloc:");exit(-1);}memcpy(_array, s._array, sizeof(DataType) * s._size);_capacity = s._capacity;_size = s._size;}~Stack(){cout << "~Stack()" << endl;if (_array){free(_array);_array = nullptr;_capacity = 0;_size = 0;}}
private:DataType *_array;size_t    _size;size_t    _capacity;
};
int main()
{Stack s1;Stack s2(s1);return 0;
}

 9ec5d26f21e04f3e805bc91cffa231e3.png

通过上面这个例子我们能得出结论,对于内置类型编译器的默认拷贝构造函数可以通过浅拷贝完成拷贝,但是像栈这样的自定义类型默认的拷贝构造函数不仅无法正确拷贝还会引发错误。

那么什么情况下需要自己写拷贝构造呢?

结论:自己实现了析构函数释放空间,就需要实现拷贝构造。

那么下面这样的代码需要自己写拷贝构造吗?

typedef int DataType;
class Stack
{
public:Stack(size_t capacity = 10){cout << "Stack(size_t capacity = 10)" << endl;_array = (DataType*)malloc(capacity * sizeof(DataType));if (nullptr == _array){perror("malloc申请空间失败");exit(-1);}_size = 0;_capacity = capacity;}void Push(const DataType& data){// CheckCapacity();_array[_size] = data;_size++;}Stack(const Stack& s){_array = (DataType*)malloc(sizeof(DataType) * s._capacity);if (_array == nullptr){perror("malloc:");exit(-1);}memcpy(_array, s._array, sizeof(DataType) * s._size);_capacity = s._capacity;_size = s._size;}~Stack(){cout << "~Stack()" << endl;if (_array){free(_array);_array = nullptr;_capacity = 0;_size = 0;}}
private:DataType *_array;size_t    _size;size_t    _capacity;
};
class MyQueue
{
public:// 默认生成构造// 默认生成析构// 默认生成拷贝构造
private:Stack _pushST;Stack _popST;int _size = 0;
};int main()
{MyQueue q1;MyQueue q2(q1);return 0;
}

08d2f009fdc74645b53dddcf42d3da05.png

4be717ce9f9846a38b7219ec38d6fbd0.png 通过上图我们就知道了答案是不需要,因为自定义类型会去调用它自己的拷贝构造,而栈的拷贝构造我们已经实现了所以不需要。

什么情况下会调用拷贝构造呢?1.显式调用(就是自己要去时候拷贝构造) 2.传值传参(比如参数是自定义类型) 3.自定义类型做返回值  。  

所以为了减少空间的消耗那么能用引用尽量去用引用。


总结

c++初学者对于学习构造函数,拷贝构造,析构函数是一大难点,所以想要真正的了解必须多练习多调试多思考,只有基础好了才能建高楼!

下一期是c++前期的重点运算符重载等的学习。

 

相关文章:

【c++】类和对象:让你明白“面向一个对象有多重要”:构造函数,析构函数,拷贝构造函数的深入学习

文章目录 什么是面向对象&#xff1f;一&#xff1a;类是什么&#xff1f; 1.类的访问限定符 2.封装 3.类的实例化 4.this指针二&#xff1a;类的6个默认成员函数 1.构造函数 2.析构函数 3.拷贝构造函数什么是面向对象&#xff1f; c语言是面向…...

职场IT老手教你3步教你玩转可视化大屏设计,让领导眼前一亮!

我是制造企业的IT中心的研发人员&#xff0c;平常工作就是配合业务部门出出报表&#xff0c;选型一些商业软件&#xff0c;并在内部负责实施运维。最近领导出去参观了一些数字化转型比较领先的工厂和制造企业&#xff0c;回来就甩给我几张图&#xff0c;问能不能我们也做几个这…...

【光伏功率预测】基于EMD-PCA-LSTM的光伏功率预测模型(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…...

大数据Kylin(二):Kylin安装使用

文章目录 Kylin安装使用 一、Kylin安装要求 二、Kylin安装 1、Kylin安装前环境准备...

我们的微服务中为什么需要网关?

说起 Spring Cloud Gateway 的使用场景&#xff0c;我相信很多小伙伴都能够脱口而出认证二字&#xff0c;确实&#xff0c;在网关中完成认证操作&#xff0c;确实是 Gateway 的重要使用场景之一&#xff0c;然而并不是唯一的使用场景。在微服务中使用网关的好处可太多了&#x…...

互联网医院源码 线上问诊 智慧医院源码 C#源码

互联网医院平台源码 智慧医院管理系统源码 开发环境&#xff1a;ASP.NET C# VS2019 SQL2008 依托于实体医院利用互联网技术对接院内业务信息系统&#xff0c;向患者提供基于线上问诊、预约挂号、缴费结算、医患互动、诊后随访、健康科普和复诊等全面的医疗健康互联网服务。…...

基于昇腾计算语言AscendCL开发AI推理应用

01 初始AscendCL AscendCL&#xff08;Ascend Computing Language&#xff0c;昇腾计算语言&#xff09;是昇腾计算开放编程框架&#xff0c;是对底层昇腾计算服务接口的封装&#xff0c;它提供运行时资源&#xff08;例如设备、内存等&#xff09;管理、模型加载与执行、算子…...

JS document.write()换行

换行效果&#xff1a; 通过传递多个参数&#xff0c;即可实现换行效果&#xff1a; document.write("<br>",ar) 效果&#xff1a; 示例源码&#xff1a; <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8&quo…...

Java高级-集合-Collection部分

本篇讲解java集合 集合 集合框架的概述 集合、数组都是对多个数据进行存储操作的结构&#xff0c;简称Java容器。 说明&#xff1a;此时的存储&#xff0c;主要指的是内存层面的存储&#xff0c;不涉及到持久化的存储&#xff08;.txt,.jpg,.avi&#xff0c;数据库中&#xf…...

Android性能优化:getResources()与Binder交火导致的界面卡顿优化

欢迎&#xff1a;https://juejin.cn/post/7198430801851531324/ 欢迎&#xff1a;https://nasdaqgodzilla.github.io/2023/02/10/Android%E6%80%A7%E8%83%BD%E4%BC%98%E5%8C%96%EF%BC%9AgetResources-%E4%B8%8EBinder%E4%BA%A4%E7%81%AB%E5%AF%BC%E8%87%B4%E7%9A%84%E7%95%8C%E…...

常见的内存操作函数

&#x1f466;个人主页&#xff1a;Weraphael ✍&#x1f3fb;作者简介&#xff1a;目前是C语言学习者 ✈️专栏&#xff1a;C语言航路 &#x1f40b; 希望大家多多支持&#xff0c;咱一起进步&#xff01;&#x1f601; 如果文章对你有帮助的话 欢迎 评论&#x1f4ac; 点赞&a…...

python关键字

文章目录1 and、or、not2 if、elif、else3 for、while4 True、False5 continue、break6 pass7 try、except、finally、raise8 import、from、as9 def、return10 class11 lambda12 del13 global、nonlocal14 in、is15 None16 assert17 with18 yield1 and、or、not and、or、not…...

C语言 | 预处理知识详解 #预处理指令有哪些?他们如何使用?宏和函数有哪些区别?...#

文章目录前言预定义符号介绍预处理指令#define#define替换规则预处理指令 #undef宏和函数的对比宏和函数的对比图命名约定命令行定义条件编译预处理指令 #include嵌套文件包含其他预处理指令写在最后前言 上篇文章介绍了一个程序运行的 编译与链接 &#xff0c;其中编译阶段有个…...

如何实现LFU缓存(最近最少频率使用)

目录 1.什么是LFU缓存&#xff1f; 2.LFU的使用场景有哪些&#xff1f; 3.LFU缓存的实现方式有哪些&#xff1f; 4.put/get 函数实现具体功能 1.什么是LFU缓存&#xff1f; LFU缓存是一个具有指定大小的缓存&#xff0c;随着添加元素的增加&#xff0c;达到容量的上限&…...

【C++之容器篇】精华:vector常见函数的接口的熟悉与使用

目录前言一、认识vector1. 介绍2. 成员类型二、默认成员函数&#xff08;Member functions&#xff09;1. 构造函数2. 拷贝构造函数vector (const vector& x);3. 析构函数4. 赋值运算符重载函数三、迭代器&#xff08;Iterators&#xff09;1. 普通对象的迭代器2. const对象…...

InstructGPT

文章目录Abstract 给定人类的命令&#xff0c;并且用人工标注想要的结果&#xff0c;构成数据集&#xff0c;使用监督学习来微调GPT-3。 然后&#xff0c;我们对模型输出进行排名&#xff0c;构成新的数据集&#xff0c;我们利用强化学习来进一步微调这个监督模型。 我们把产…...

RTOS之一环境搭建(基于TM4C123GXL)

硬件TM4C123GXLBOOSTXL-EDUMKII keil5micriumOSA软件安装&#xff1a;1 ARM-MDK(MDK538aMDK_Stellaris_ICDI_AddOn)MDK538a链接&#xff1a;https://www.keil.com/demo/eval/arm.htmICDI链接&#xff1a;https://documentation-service.arm.com/static/60509bd61da8f8344a2ca1b…...

151、【动态规划】AcWing ——2. 01背包问题:二维数组+一维数组(C++版本)

题目描述 原题链接&#xff1a;2. 01背包问题 解题思路 &#xff08;1&#xff09;二维dp数组 动态规划五步曲&#xff1a; &#xff08;1&#xff09;dp[i][j]的含义&#xff1a; 容量为j时&#xff0c;从物品1-物品i中取物品&#xff0c;可达到的最大价值 &#xff08;2…...

DS期末复习卷(二)

选择题 1&#xff0e;下面关于线性表的叙述错误的是&#xff08; D &#xff09;。 (A) 线性表采用顺序存储必须占用一片连续的存储空间 (B) 线性表采用链式存储不必占用一片连续的存储空间 © 线性表采用链式存储便于插入和删除操作的实现 (D) 线性表采用顺序存储便于插…...

大数据技术架构(组件)31——Spark:Optimize--->JVM On Compute

2.1.9.4、Optimize--->JVM On Compute首要的一个问题就是GC,那么先来了解下其原理&#xff1a;1、内存管理其实就是对象的管理&#xff0c;包括对象的分配和释放&#xff0c;如果显式的释放对象&#xff0c;只要把该对象赋值为null&#xff0c;即该对象变为不可达.GC将负责回…...

Redis相关知识总结(缓存雪崩,缓存穿透,缓存击穿,Redis实现分布式锁,如何保持数据库和缓存一致)

文章目录 1.什么是Redis&#xff1f;2.为什么要使用redis作为mysql的缓存&#xff1f;3.什么是缓存雪崩、缓存穿透、缓存击穿&#xff1f;3.1缓存雪崩3.1.1 大量缓存同时过期3.1.2 Redis宕机 3.2 缓存击穿3.3 缓存穿透3.4 总结 4. 数据库和缓存如何保持一致性5. Redis实现分布式…...

【算法训练营Day07】字符串part1

文章目录 反转字符串反转字符串II替换数字 反转字符串 题目链接&#xff1a;344. 反转字符串 双指针法&#xff0c;两个指针的元素直接调转即可 class Solution {public void reverseString(char[] s) {int head 0;int end s.length - 1;while(head < end) {char temp …...

镜像里切换为普通用户

如果你登录远程虚拟机默认就是 root 用户&#xff0c;但你不希望用 root 权限运行 ns-3&#xff08;这是对的&#xff0c;ns3 工具会拒绝 root&#xff09;&#xff0c;你可以按以下方法创建一个 非 root 用户账号 并切换到它运行 ns-3。 一次性解决方案&#xff1a;创建非 roo…...

2025盘古石杯决赛【手机取证】

前言 第三届盘古石杯国际电子数据取证大赛决赛 最后一题没有解出来&#xff0c;实在找不到&#xff0c;希望有大佬教一下我。 还有就会议时间&#xff0c;我感觉不是图片时间&#xff0c;因为在电脑看到是其他时间用老会议系统开的会。 手机取证 1、分析鸿蒙手机检材&#x…...

Matlab | matlab常用命令总结

常用命令 一、 基础操作与环境二、 矩阵与数组操作(核心)三、 绘图与可视化四、 编程与控制流五、 符号计算 (Symbolic Math Toolbox)六、 文件与数据 I/O七、 常用函数类别重要提示这是一份 MATLAB 常用命令和功能的总结,涵盖了基础操作、矩阵运算、绘图、编程和文件处理等…...

学习STC51单片机32(芯片为STC89C52RCRC)OLED显示屏2

每日一言 今天的每一份坚持&#xff0c;都是在为未来积攒底气。 案例&#xff1a;OLED显示一个A 这边观察到一个点&#xff0c;怎么雪花了就是都是乱七八糟的占满了屏幕。。 解释 &#xff1a; 如果代码里信号切换太快&#xff08;比如 SDA 刚变&#xff0c;SCL 立刻变&#…...

A2A JS SDK 完整教程:快速入门指南

目录 什么是 A2A JS SDK?A2A JS 安装与设置A2A JS 核心概念创建你的第一个 A2A JS 代理A2A JS 服务端开发A2A JS 客户端使用A2A JS 高级特性A2A JS 最佳实践A2A JS 故障排除 什么是 A2A JS SDK? A2A JS SDK 是一个专为 JavaScript/TypeScript 开发者设计的强大库&#xff…...

深度学习水论文:mamba+图像增强

&#x1f9c0;当前视觉领域对高效长序列建模需求激增&#xff0c;对Mamba图像增强这方向的研究自然也逐渐火热。原因在于其高效长程建模&#xff0c;以及动态计算优势&#xff0c;在图像质量提升和细节恢复方面有难以替代的作用。 &#x1f9c0;因此短时间内&#xff0c;就有不…...

es6+和css3新增的特性有哪些

一&#xff1a;ECMAScript 新特性&#xff08;ES6&#xff09; ES6 (2015) - 革命性更新 1&#xff0c;记住的方法&#xff0c;从一个方法里面用到了哪些技术 1&#xff0c;let /const块级作用域声明2&#xff0c;**默认参数**&#xff1a;函数参数可以设置默认值。3&#x…...

何谓AI编程【02】AI编程官网以优雅草星云智控为例建设实践-完善顶部-建立各项子页-调整排版-优雅草卓伊凡

何谓AI编程【02】AI编程官网以优雅草星云智控为例建设实践-完善顶部-建立各项子页-调整排版-优雅草卓伊凡 背景 我们以建设星云智控官网来做AI编程实践&#xff0c;很多人以为AI已经强大到不需要程序员了&#xff0c;其实不是&#xff0c;AI更加需要程序员&#xff0c;普通人…...