c++类和对象

news/2024/5/1 8:35:25/文章来源:https://blog.csdn.net/includeevey/article/details/127151939

前言

        在学习完漫长的C语言,那么这篇文章也算是开始踏上了高级语言之路 。古人云:路漫漫其修远兮,吾将上下而求索。c++的道路才开始,那么我们应该为此开始思考了。余甚 愚,余认为c++有太多细节了,必定耗时细磨才能将它掌握。

关于《类和对象》就用这一篇文章呈现,可能会比较长,但有目录就更易查阅。误导便是若发现有何问题,欢迎随时不吝指正,这里就谢谢大家观看了。

面向过程和面向对象初步认识

有个很有意思的段子,就是关于《把大象装进冰箱需要几步》

在面向过程:①打开冰箱→②把大象塞进去→③关上冰箱

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

面向对象:把冰箱看成是一个对象,把大象也看成是一个对象,通过操作大象和冰箱这两个对象,完成将大象放入冰箱的过程

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


类的引入

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

c语言实现

Stack.h

#define _CRT_SECURE_NO_WARNINGS
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <stdbool.h>typedef int STDataType; //#define N 10
//typedef struct Stack
//{
//	   STDataType _a[N]; 
//	   int _top; // 栈顶
//}Stack;// 支持动态增长的栈
typedef int STDataType;
typedef struct Stack
{STDataType* _a;int _top;       // 栈顶int _capacity;  // 容量 
}Stack;
// 初始化栈 
void StackInit(Stack* ps); 
// 入栈
void StackPush(Stack* ps, STDataType data); 
// 出栈 
void StackPop(Stack* ps); 
// 获取栈顶元素 
STDataType StackTop(Stack* ps); 
// 获取栈中有效元素个数 
int StackSize(Stack* ps); 
// 检测栈是否为空,如果为空返回非零结果,如果不为空返回0 
bool StackEmpty(Stack* ps);
// 销毁栈 
void StackDestroy(Stack* ps);

Stack.c 

#define _CRT_SECURE_NO_WARNINGS
#include "Stack.h"// 初始化栈 
void StackInit(Stack* ps)
{assert(ps);//断言传入地址是否为空ps->_a = NULL;ps->_capacity = ps->_top = 0;
}// 入栈
void StackPush(Stack* ps, STDataType data)
{assert(ps);if (ps->_top == ps->_capacity){int newCapacity = ps->_capacity == 0 ? 4 : ps->_capacity*2;//判断容量是否为空并设置增加容量数量STDataType* temp = (STDataType*)realloc(ps->_a, newCapacity*sizeof(STDataType));//增加容量if (temp == NULL)//判断地址是否开辟成功{perror("realloc fail");exit(-1);}ps->_a = temp;//赋址与结构体中ps->_capacity = newCapacity;//更新容量}ps->_a[ps->_top] = data;//数据入栈ps->_top++;//栈顶++
}// 出栈 
void StackPop(Stack* ps)
{assert(ps);assert(!StackEmpty(ps));//断言栈是否为空--ps->_top;//栈顶--
}// 获取栈顶元素 
STDataType StackTop(Stack* ps)
{assert(ps);assert(!StackEmpty(ps));return ps->_a[ps->_top-1];
}// 获取栈中有效元素个数 
int StackSize(Stack* ps)
{assert(ps);return ps->_top;
}// 检测栈是否为空,如果为空返回非零结果,如果不为空返回0 
bool StackEmpty(Stack* ps)
{assert(ps);return ps->_top==0;
}// 销毁栈 
void StackDestroy(Stack* ps)
{assert(ps);free(ps->_a);//清除数组地址ps->_a = NULL;ps->_top = ps->_capacity = 0;
}

c++实现

typedef int DataType;struct Stack{void Init(size_t capacity){_array = (DataType*)malloc(sizeof(DataType) * capacity);if (nullptr == _array){perror("malloc申请空间失败");return;}_capacity = capacity;_size = 0;}void Push(const DataType& data){// 扩容_array[_size] = data;++_size;}DataType Top(){
return _array[_size - 1];}void Destroy(){if (_array){free(_array);_array = nullptr;_capacity = 0;_size = 0;}}DataType* _array;size_t _capacity;size_t _size;
};int main()
{Stack s;s.Init(10);s.Push(1);s.Push(2);s.Push(3);cout << s.Top() << endl;s.Destroy();return 0;
}

类的定义

class为定义类的关键字,ClassName为类的名字,{}中为类的主体,注意类定义结束时后面分号不能省略。

类体中内容称为类的成员:类中的变量称为类的属性或成员变量; 类中的函数称为类的方法或者成员函数。

class className

{

         // 类体:由成员函数和成员变量组成

};       // 一定要注意后面的分号

 类的两种定义方式:

  1. 声明和定义全部放在类体中,需要注意:成员函数如果在类中定义,编译器可能会将其当成内联函数处理。
  2. 声明在.h文件中,类的定义放在.cpp文件中。

情况一

情况二

成员变量命名规则的建议:

我们看看这个函数,是不是很僵硬?

class Date{public:void Init(int year){// 这里的year到底是成员变量,还是函数形参?year = year;}private:int year;
};

所以一般都建议这样

class Date{public:void Init(int year){_year = year;}private:int _year;
};// 或者这样class Date{public:void Init(int year){mYear = year;}private:int mYear;
};// 其他方式也可以的,主要看公司要求。一般都是加个前缀或者后缀标识区分就行。

类的访问限定符及封装

访问限定符

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

【访问限定符说明】

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

2. protected和private修饰的成员在类外不能直接被访问(此处protected和private是类似的)

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

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

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

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

【面试题】

问题:C++中struct和class的区别是什么?

解答:

        C++需要兼容C语言,所以C++中struct可以当成结构体使用。另外C++中struct还可以用来定义类。和class定义类是一样的,区别是struct定义的类默认访问权限是public,class定义的类默认访问权限是private。注意:在继承和模板参数列表位置,struct和class也有区别,后序给大 家介绍。

封装

【面试题】

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

在类和对象阶段,主要是研究类的封装特性,那什么是封装呢?

封装:将数据和操作数据的方法进行有机结合,隐藏对象的属性和实现细节,仅对外公开接口来 和对象进行交互。       

封装本质上是一种管理,让用户更方便使用类。比如:对于电脑这样一个复杂的设备,提供给用 户的就只有开关机键、通过键盘输入,显示器,USB插孔等,让用户和计算机进行交互,完成日 常事务。但实际上电脑真正工作的却是CPU、显卡、内存等一些硬件元件。    

     对于计算机使用者而言,不用关心内部核心部件,比如主板上线路是如何布局的,CPU内部是如 何设计的等,用户只需要知道,怎么开机、怎么通过键盘和鼠标与计算机进行交互即可。因此计 算机厂商在出厂时,在外部套上壳子,将内部实现细节隐藏起来,仅仅对外提供开关机、鼠标以 及键盘插孔等,让用户可以与计算机进行交互即可。  

在C++语言中实现封装,可以通过类将数据以及操作数据的方法进行有机结合,通过访问权限来 隐藏对象内部实现细节,控制哪些方法可以在类外部直接被使用。

类的作用域

类定义了一个新的作用域,类的所有成员都在类的作用域中。在类体外定义成员时,需要使用 ::

作用域操作符指明成员属于哪个类域。

class Person{public:void PrintPersonInfo();private:char _name[20];char _gender[3];int  _age;
};// 这里需要指定PrintPersonInfo是属于Person这个类域void Person::PrintPersonInfo()
{cout << _name << " "<< _gender << " " << _age << endl;
}

类的实例化

用类类型创建对象的过程,称为类的实例化

1. 类是对对象进行描述的,是一个模型一样的东西,限定了类有哪些成员,定义出一个类并没有分配实际的内存空间来存储它;比如:入学时填写的学生信息表,表格就可以看成是一个类,来描述具体学生信息。

类就像谜语一样,对谜底来进行描述,谜底就是谜语的一个实例。

谜语:"年纪不大,胡子一把,主人来了,就喊妈妈" 谜底:山羊

2. 一个类可以实例化出多个对象,实例化出的对象占用实际的物理空间,存储类成员变量

int main()
{Person._age = 100;   // 编译失败:error C2059: 语法错误:“.”return 0;
}

Person类是没有空间的,只有Person类实例化出的对象才有具体的年龄。

3. 做个比方。类实例化出对象就像现实中使用建筑设计图建造出房子,类就像是设计图,只设计出需要什么东西,但是并没有实体的建筑存在,同样类也只是一个设计,实例化出的对象才能实际存储数据,占用物理空间

类对象模型

如何计算类对象的大小

问题:类中既可以有成员变量,又可以有成员函数,那么一个类的对象中包含了什么?如何计算 一个类的大小?

类对象的存储方式猜测

1.对象中包含类的各个成员

缺陷:每个对象中成员变量是不同的,但是调用同一份函数,如果按照此种方式存储,当一 个类创建多个对象时,每个对象中都会保存一份代码,相同代码保存多次,浪费空间。那么 如何解决呢?

2.代码只保存一份,在对象中保存存放代码的地址

3. 只保存成员变量,成员函数存放在公共的代码段

问题:对于上述三种存储方式,那计算机到底是按照那种方式来存储的?

我们再通过对下面的不同对象分别获取大小来分析看下

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

sizeof(A1) : ___4___ sizeof(A2) : ___1___ sizeof(A3) : ___1___

结论:

一个类的大小,实际就是该类中”成员变量”之和,当然要注意内存对齐

注意空类的大小,空类比较特殊,编译器给了空类一个字节来唯一标识这个类的对象。

结构体内存对齐规则 

1. 第一个成员在与结构体偏移量为0的地址处。

2. 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。

注意:对齐数 = 编译器默认的一个对齐数 与 该成员大小的较小值。VS中默认的对齐数为8

3. 结构体总大小为:最大对齐数(所有变量类型最大者与默认对齐参数取最小)的整数倍。

4. 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。

【面试题】

1. 结构体怎么对齐? 为什么要进行内存对齐?

因为在32位操作系统(虽然64位操作系统,但是为了保证兼容性,编程仍然主要考量32位)中,数据总线是32位,地址总线是32位。地址总线是32位,意味着寻址空间是按4递增的;数据总线32位意味着一次可读写4byte。

视线拉回我们32位cpu,32位/8位=4字节,所以cpu一次工作可以取到4个字节的数据。那以读取的角度排布我们内存的话可以像下面这样。一次cpu读一行数据。

当我们不对齐时操作系统是需要读取到完整的i需要读取两次(或者说两行)然后拼接再一起,I/O操作是很耗时的,这是很浪费时间的。如果需要更快读到数据,那一个数据最好是存在一整行,像上面对齐那样。实则就是用空间换时间,优点是提高了可移植性和cpu性能

2. 如何让结构体按照指定的对齐参数进行对齐?能否按照3、4、5即任意字节对齐?

(1)  #pragma pack

(2) 可以设置,具体看硬件

平台原因(移植原因): 不是所有的硬件平台都能访问任意地址上的任意数据
的;某些硬件平台只能 在某些地址处取某些特定类型的数据,否则抛出硬件异常

性能原因:数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。

3. 什么是大小端?如何测试某台机器是大端还是小端,有没有遇到过要考虑大小端的场景

http://t.csdn.cn/3BTWs


#include <sdtio.h>
int check_sys()
{int i = 1;if (*(char*)&i == 1){return 1;}else{return 0;}
}
int main()
{int ret = check_sys();if (ret = 1){printf("小端\n");}else{printf("大端\n");}return 0;
}

this指针

this指针的引出

先来定义一个日期类 Date

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, d2;d1.Init(2022, 1, 11);d2.Init(2022, 1, 12);d1.Print();d2.Print();return 0;
}

对于上述类,有这样的一个问题:

Date类中有 Init 与 Print 两个成员函数,函数体中没有关于不同对象的区分,那当d1调用 Init 函 数时,该函数是如何知道应该设置d1对象,而不是设置d2对象呢?

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

this指针的特性

1. this指针的类型:类类型* const,即成员函数中,不能给this指针赋值。

2. 只能在“成员函数”的内部使用

3. this指针本质上是“成员函数”的形参,当对象调用成员函数时,将对象地址作为实参传递给

this形参。所以对象中不存储this指针

4. this指针是“成员函数”第一个隐含的指针形参,一般情况由编译器通过ecx寄存器自动传 递,不需要用户传递

【面试题】  

1. this指针存在哪里?

其实编译器在生成程序时加入了获取对象首地址的相关代码。编译器有并把获取的首地址存放在了寄存器ECX中(VC++编译器是放在ECX中,其它可能不同)。也就是成员函数的其它参数正常都是存放在栈中。而this指针参数则是存放在寄存器中。类的静态成员函数因为没有this指针这个参数,所以类的静态成员函数也就无法调用类的非静态成员变量。

2. this指针可以为空吗?

this可以为空,当我们在调用函数的时候,如果函数内部并不需要使用到this,也就是不需要通过this指向当前对象并对其进行操作时才可以为空(当我们在其中什么都不放或者在里面随便打印一个字符串),如果调用的函数需要指向当前对象,并进行操作,则会发生错误(空指针引用)就跟C中一样不能进行空指针的引用。

3.下面程序编译运行结果是? A、编译报错 B、运行崩溃 C、正常运行

class A
{
public:void Print(){cout << "Print()" << endl;}private:int _a;
};int main()
{A* p = nullptr;p->Print();return 0;
}

正常运行:p不发生解引用,因为成员函数的地址不存在对象中,在公共代码区域。这里p为空指针传入print中,然后this接受print地址直接输入。

4.下面程序编译运行结果是? A、编译报错 B、运行崩溃 C、正常运行

class A
{
public:void PrintA(){cout << _a << endl;}
private:int _a;
};int main()
{A* p = nullptr;p->PrintA();return 0;
}

运行崩溃:前面一样,this接受的是_a,因为-a是成员变量会对print的p进行解引用

C语言和C++实现Stack的对比

C语言实现

#include <iostream>
#include <assert.h>typedef int DataType;typedef struct Stack{DataType* array;int capacity;int size;
}Stack;void StackInit(Stack* ps)
{assert(ps);ps->array = (DataType*)malloc(sizeof(DataType)* 3);if (NULL == ps->array){assert(0);return;} ps->capacity = 3;ps->size = 0;
}void StackDestroy(Stack* ps)
{assert(ps);if (ps->array){free(ps->array);ps->array = NULL;ps->capacity = 0;ps->size = 0;}
}void CheckCapacity(Stack* ps)
{if (ps->size == ps->capacity){int newcapacity = ps->capacity * 2;DataType* temp = (DataType*)realloc(ps->array,newcapacity*sizeof(DataType));if (temp == NULL){perror("realloc申请空间失败!!!");return;}ps->array = temp;ps->capacity = newcapacity;}
}void StackPush(Stack* ps, DataType data)
{assert(ps);CheckCapacity(ps);ps->array[ps->size] = data;ps->size++;
}int StackEmpty(Stack* ps)
{assert(ps);return 0 == ps->size;
}void StackPop(Stack* ps)
{if (StackEmpty(ps))return;ps->size--;
}DataType StackTop(Stack* ps)
{assert(!StackEmpty(ps));return ps->array[ps->size - 1];}int StackSize(Stack* ps)
{assert(ps);return ps->size;
}int main()
{Stack s;StackInit(&s);StackPush(&s, 1);StackPush(&s, 2);StackPush(&s, 3);StackPush(&s, 4);printf("%d\n", StackTop(&s));printf("%d\n", StackSize(&s));StackPop(&s);StackPop(&s);printf("%d\n", StackTop(&s));printf("%d\n", StackSize(&s));StackDestroy(&s);return 0;
}

C++实现

#include <iostream>
typedef int DataType;class Stack{public:void Init(){_array = (DataType*)malloc(sizeof(DataType)* 3);if (NULL == _array){perror("malloc申请空间失败!!!");return;}_capacity = 3;_size = 0;} void Push(DataType data){CheckCapacity();_array[_size] = data;_size++;}void Pop(){if (Empty())return;_size--;}DataType Top(){ return _array[_size - 1]; }int Empty() { return 0 == _size; }int Size(){ return _size; }void Destroy(){if (_array){free(_array);_array = NULL;_capacity = 0;_size = 0;}}private:void CheckCapacity(){if (_size == _capacity){int newcapacity = _capacity * 2;DataType* temp = (DataType*)realloc(_array, newcapacity *sizeof(DataType));if (temp == NULL){perror("realloc申请空间失败!!!");return;}_array = temp;_capacity = newcapacity;}}private:DataType* _array;int _capacity;int _size;
};int main()
{Stack s;s.Init();s.Push(1);s.Push(2);s.Push(3);s.Push(4);printf ("%d\n", s.Top());printf("%d\n", s.Size());s.Pop();s.Pop();printf("%d\n", s.Top());printf("%d\n", s.Size());s.Destroy();return 0;
}

类的6个默认成员函数

如果一个类中什么成员都没有,简称为空类。

空类中真的什么都没有吗?并不是,任何类在什么都不写时,编译器会自动生成以下6个默认成员 函数。

默认成员函数:用户没有显式实现,编译器会生成的成员函数称为默认成员函数。

构造函数

概念

对于以下Date类:

#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;class Date
{
public:void Inti(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.Inti(2022, 10, 5);d1.Print();Date d2;d2.Inti(2022, 10, 6);d2.Print();printf("%p\n%p", d1, d2);return 0;
}

对于Date类,可以通过 Init 公有方法给对象设置日期,但如果每次创建对象时都调用该方法设置 信息,未免有点麻烦,那能否在对象创建时,就将信息设置进去呢?

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

特性

构造函数是特殊的成员函数,需要注意的是,构造函数虽然名称叫构造,但是构造函数的主要任 务并不是开空间创建对象,而是初始化对象

其特征如下:

1. 函数名与类名相同。

2. 无返回值。

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

4. 构造函数可以重载。

#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;class Date
{
public://void Inti(int year, int month, int day)//{//	_year = year;//	_month = month;//	_day = day;//}//Date()//{//}Date(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(2022, 10, 6);d2.Print();return 0;
}

【注意】如果通过无参构造函数创建对象时,对象后面不用跟括号,否则就成了函数声明

5. 如果类中没有显式定义构造函数,则C++编译器会自动生成一个无参的默认构造函数,一旦用户显式定义编译器将不再生成。  

class Date{
public:/*// 如果用户显式定义了构造函数,编译器将不再生成Date(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类中构造函数屏蔽后,代码可以通过编译,因为编译器生成了一个无参的默认构造函数// 将Date类中构造函数放开,代码编译失败,因为一旦显式定义任何构造函数,编译器将不再生成// 无参构造函数,放开后报错:error C2512: “Date”: 没有合适的默认构造函数可用Date d1;d1.Print();return 0;
}

【注意】:当自动生成的默认构造函数的默认值(函数重载)是随机值

默认构造函数中函数重载有什么用呢?

解答:提供多个构造函数,多个初始化方式

class Date
{
public:Date(){_year = 1;_month = 2;_day = 3;}void Print(){cout << _year << "-" << _month << "-" << _day << endl;}private:int _year;int _month;int _day;
};int main()
{Date d1;d1.Print();return 0;
}

6.关于编译器生成的默认成员函数,不实现构造函数的情况下,编译器会 生成默认的构造函数。但是看起来默认构造函数又没什么用?d对象调用了编译器生成的默 认构造函数,但是d对象_year/_month/_day,依旧是随机值。也就说在这里编译器生成的 默认构造函数并没有什么用?

解答:C++把类型分成内置类型(基本类型)和自定义类型。内置类型就是语言提供的数据类 型,如:int/char...,自定义类型就是我们使用class/struct/union等自己定义的类型,看看 下面的程序,就会发现编译器生成默认的构造函数会对自定类型成员_t调用的它的默认构造函数。内置类型不处理,指定类型会处理。

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

【注意】:C++11 中针对内置类型成员不初始化的缺陷,又打了补丁,即:内置类型成员变量在 类中声明时可以给默认值。

_second = 0;}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;
}

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

class Date
{
public://Date()//{//	_year = 1900;//	_month = 1;//	_day = 1;//}Date(int year = 1900, int month = 1, int day = 1){_year = year;_month = month;_day = day;}private:int _year;int _month;int _day;
};// 以下测试函数能通过编译吗?void Test()
{Date d1;
}

析构函数

概念

通过前面构造函数的学习,我们知道一个对象是怎么来的,那一个对象又是怎么没呢的? 析构函数:与构造函数功能相反,析构函数不是完成对对象本身的销毁,局部对象销毁工作是由 编译器完成的。而对象在销毁时会自动调用析构函数,完成对象中资源的清理工作。

特性

1. 析构函数名是在类名前加上字符 ~。

2. 无参数无返回值类型。

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

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

typedef int DataType;class Stack
{
public:Stack(size_t capacity = 3){_array = (DataType*)malloc(sizeof(DataType)* capacity);if (NULL == _array){perror("malloc申请空间失败!!!");return;}_capacity = capacity;_size = 0;}void Push(DataType data){// CheckCapacity();_array[_size] = data;_size++;}// 其他方法...~Stack(){if (_array){free(_array);_array = NULL;_capacity = 0;_size = 0;}}private:DataType* _array;int _capacity;int _size;
};void TestStack()
{Stack s;s.Push(1);s.Push(2);
}

5. 关于编译器自动生成的析构函数,是否会完成一些事情呢?下面的程序我们会看到,编译器 生成的默认析构函数,对自定类型成员调用它的析构函数。

class Time
{
public:~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;
}// 程序运行结束后输出:~Time()
// 在main方法中根本没有直接创建Time类的对象,为什么最后会调用Time类的析构函数?// 因为:main方法中创建了Date对象d,而d中包含4个成员变量,其中_year, _month, _day三个是// 内置类型成员,销毁时不需要资源清理,最后系统直接将其内存回收即可;而_t是Time类对象,所以在
// d销毁时,要将其内部包含的Time类的_t对象销毁,所以要调用Time类的析构函数。但是:main函数// 中不能直接调用Time类的析构函数,实际要释放的是Date类对象,所以编译器会调用Date类的析构函// 数,而Date没有显式提供,则编译器会给Date类生成一个默认的析构函数,目的是在其内部调用Time
// 类的析构函数,即当Date对象销毁时,要保证其内部每个自定义对象都可以正确销毁// main函数中并没有直接调用Time类析构函数,而是显式调用编译器为Date类生成的默认析构函数// 注意:创建哪个类的对象则调用该类的析构函数,销毁那个类的对象则调用该类的析构函数

6. 如果类中没有申请资源时,析构函数可以不写,直接使用编译器生成的默认析构函数,比如

Date类;有资源申请时,一定要写,否则会造成资源泄漏,比如Stack类。

拷贝构造函数

概念

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

特征

1. 拷贝构造函数是构造函数的一个重载形式

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

class Date{public:Date(int year = 1900, int month = 1, int day = 1){_year = year;_month = month;_day = day;}// Date(const Date& d)   // 正确写法Date(const Date& d) {_year = d._year;_month = d._month;_day = d._day;}private:int _year;int _month;int _day;
};int main()
{Date d1;Date d2(d1);return 0;
}

3. 若未显式定义,编译器会生成默认的拷贝构造函数。 默认的拷贝构造函数对象按内存存储按 字节序完成拷贝,这种拷贝叫做浅拷贝,或者值拷贝。  

class Time
{
public:Time(){_hour = 1;_minute = 1;_second = 1;}Time(const Time& t){_hour = t._hour;_minute = t._minute;_second = t._second;cout << "Time::Time(const 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 d1;// 用已经存在的d1拷贝构造d2,此处会调用Date类的拷贝构造函数// 但Date类并没有显式定义拷贝构造函数,则编译器会给Date类生成一个默认的拷贝构造函数Date d2(d1);return 0;
}

【注意】:在编译器生成的默认拷贝构造函数中,内置类型是按照字节方式直接拷贝的,而自定 义类型是调用其拷贝构造函数完成拷贝的。

4. 编译器生成的默认拷贝构造函数已经可以完成字节序的值拷贝了,还需要自己显式实现吗? 当然像日期类这样的类是没必要的。那么下面的类呢?  

这里会发现下面的程序会崩溃掉,这里就需要我们以后讲的深拷贝去解决。

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

【注意】:类中如果没有涉及资源申请时,拷贝构造函数是否写都可以;一旦涉及到资源申请 时,则拷贝构造函数是一定要写的,否则就是浅拷贝。

拷贝构造函数典型调用场景:

1.使用已存在对象创建新对象

2.函数参数类型为类类型对象

3.函数返回值类型为类类型对象

       

感谢大家支持,这篇文章还会次序更新,将会把类和对象讲解完! 

                                

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

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

相关文章

实验一:贝叶斯神经网络及其如何用随机梯度马尔可夫链蒙特卡洛有效训练

0.实验环境搭建&#xff1a; 源代码获取&#xff1a; 来源一&#xff1a;google 来源二&#xff1a;web 来源三&#xff1a;github 环境&#xff1a; conda create --name python36_google_deep python3.6 conda activate python36_google_deep #建议按照顺序安装 pip inst…

基于FPGA的图像边缘检测

基于FPGA的图像边缘检测一、图像处理算法1.灰度转换2.高斯滤波3.二值化4.Sobel二、项目框架1.摄像头配置模块2.图像处理模块3.数据缓存模块4.其它模块三、部分代码1.数据采集模块2.读写控制模块四、参考五、源码简介&#xff1a;基于FPGA&#xff0c;摄像头实时采集图像数据&am…

【Algorithm】Karatsuba Multiplications 乘法算法

Karatsuba Multiplications Q1&#xff1a; 请计算&#xff1a;x1234x1234x1234, y5678y5678y5678, x∗y?x*y?x∗y? 这个问题其实我们在三年级的时候就学过&#xff0c;用乘法竖式进行运算。但是有没有其他的方法&#xff0c;或者说&#xff0c;如果 x,yx,yx,y 非常大的时候…

drf 视图类 GenericAPIView 及扩展

drf 视图类 GenericAPIView 及扩展 文章目录drf 视图类 GenericAPIView 及扩展1、2个视图基类1.1、GenericAPIView&#xff1a;属性和方法1.2、基于APIView 写5个接口1.3、基于GenericAPIView写5个接口2、5个视图扩展类2.1 基于GenericAPIView5个视图扩展类写接口3、九个视图子…

【UCB操作系统CS162项目】Pintos Lab2:用户程序 User Programs(下)

在上节中&#xff0c;我们已经完成了 Lab 2 要求的参数传递和系统调用中的 halt, exit 以及向 stdout 输出的 write&#xff0c;最终停在了 wait 的实现之前。本节就先从 wait 和 exec 继续。 Syscall wait exec&#xff1a;实现父子进程 讲义中 wait 的要求是这样的&#x…

这几个文字翻译工具确定不试试看?

想问问大家平常会接触到TXT文件吗&#xff1f;这是微软在操作系统上附带的一种文本格式&#xff0c;主要是保存纯文字信息&#xff0c;像我们电脑上自带的记事本工具&#xff0c;就是使用这种文件格式。有时候我们需要将文本内容翻译成中文。那你知道如何实现TXT翻译成中文吗&a…

LRU缓存——哈希表+双向链表

一、题目描述 请你设计并实现一个满足 LRU (最近最少使用) 缓存 约束的数据结构。 实现 LRUCache 类&#xff1a; 1&#xff09;LRUCache(int capacity) 以 正整数 作为容量 capacity 初始化 LRU 缓存 2&#xff09;int get(int key) 如果关键字 key 存在于缓存中&#xff0c;…

STA系列 - 特殊时序分析multicycle/half-cycle/false path

文章目录什么是require time/arrive timeMulticycle PathHalf PathFalth Path本篇文章介绍的是特殊的时序path, 全文为视频笔记&#xff0c;以及自己的理解https://www.bilibili.com/video/BV1if4y1p7Dq?p10&vd_source84d1070e8334ce7e2bb0bd110abcf1a7什么是require time…

使用服务器跑模型——案例2

案例2 在本案例中我们使用vscode来上传/下载文件&#xff0c;服务器端编程和debug。 下载 vscode 在官网下载vscode正式版&#xff0c;别使用家庭版。下载地址https://code.visualstudio.com/Download。 使用 vscode 连接服务器 在vscode扩展中搜索ssh并下载安装。 安装成功…

【机器学习】熵权算法确定权重 原理+完整MATLAB代码+详细注释+操作实列

【机器学习】熵权算法确定权重 原理完整MATLAB代码详细注释操作实列 文章目录 1. 熵权法确定指标权重 &#xff08;1&#xff09;构造评价矩阵 Ymn &#xff08;2&#xff09;评价矩阵标准化处理 &#xff08;3&#xff09;计算指标信息熵值 Mj &#xff08;4&#xff09…

原生JS项目练习——验证码的生成及教验

一、主要功能介绍&#xff1a; 1、通过for循环生成生成六位随机验证码 2、通过for循环随机生成验证码颜色 3、窗口加载事件&#xff0c;窗口一加载就调用函数&#xff0c;重置验证码 4、按钮点击事件&#xff0c;一点击就调用函数&#xff0c;重置验证码 5、input输入框已失去焦…

Yarn概述

Hadoop系列 注&#xff1a;大家觉得博客好的话&#xff0c;别忘了点赞收藏呀&#xff0c;本人每周都会更新关于人工智能和大数据相关的内容&#xff0c;内容多为原创&#xff0c;Python Java Scala SQL 代码&#xff0c;CV NLP 推荐系统等&#xff0c;Spark Flink Kafka Hbase…

公众号网课搜题接口系统

公众号网课搜题接口系统 本平台优点&#xff1a; 多题库查题、独立后台、响应速度快、全网平台可查、功能最全&#xff01; 1.想要给自己的公众号获得查题接口&#xff0c;只需要两步&#xff01; 2.题库&#xff1a; 查题校园题库&#xff1a;查题校园题库后台&#xff08;…

快速开发微信小程序之二-微信支付

一、背景 在面试程序员的时候&#xff0c;有两项经历会带来比较大的加分&#xff0c;第一你是否做过支付金融相关的业务&#xff0c;第二你是否写过底层框架中间件代码&#xff0c;今天我们聊一下微信支付是如何对接的。 二、相关概念 1、微信商户平台 要使用微信支付&#…

一、mini2440_bsp_led

一、芯片手册 1、板子原理图 2、GPIO使用 &#xff08;1&#xff09;GPxCON &#xff08;2&#xff09;GPxDAT 二、实现分析 1、初始化led 设置GPBCON&#xff08;0x56000010&#xff09;为 0x00015400 2、设置led输出&#xff0c;根据原理图引脚输出低电平时灯被点亮 LED1…

K8s-临时容器 Ephemeral Containers

临时容器 Ephemeral Containers 当由于容器崩溃或容器镜像不包含调试工具而导致 kubectl exec 无用时&#xff0c; 临时容器对于交互式故障排查很有用。尤其是&#xff0c;Distroless 镜像 允许用户部署最小的容器镜像&#xff0c;从而减少攻击面并减少故障和漏洞的暴露。 由于…

C | 枚举?看一遍就够了

CSDN话题挑战赛第2期 参赛话题&#xff1a;学习笔记 啊我摔倒了..有没有人扶我起来学习.... 目录前言枚举1. 枚举的定义2. 枚举的内存大小3. 枚举的优势4. 枚举需要注意的地方前言 结构体、枚举、联合体都是自定义类型&#xff0c;结构体主要知识点结构体内存对齐可参考《C | …

九月SLAM相关论文速递

九月SLAM相关论文速递 论文列表DirectTracker: 3D Multi-Object Tracking Using Direct Image Alignment and Photometric Bundle Adjustment3D VSG: Long-term Semantic Scene Change Prediction through 3D Variable Scene GraphsLeveraging Large Language Models for Robo…

使用服务器跑模型——案例1

案例1 该方法mac&#xff0c;linux&#xff0c;windows都通用。我们使用terminal or cmd进行操作。 假设我们本地具有一个需要跑的模型Unet&#xff0c;我们需要将该模型上传到服务器上跑&#xff0c;步骤如下&#xff1a; 使用tar压缩文件 我们定位到我们需要压缩的模型&a…

云原生之容器编排实践-以k8s的Service方式暴露SpringBoot服务

背景 上一篇文章云原生之容器编排实践-SpringBoot应用以Deployment方式部署到minikube以及弹性伸缩中&#xff0c;我们通过 Deployment 完成了将 SpringBoot 应用部署到 minikube 并测试了其弹性伸缩的丝滑体验。但是 Deployment 部署后我们还面临以下问题&#xff1a; 访问时…