C++构造函数和析构函数略讲

目录

typeid(变量名).name()函数

作用:

auto

auto的意义所在

auto的注意事项

auto不能做函数的参数和返回值

auto也不能做数组的声明(规定)

auto的另一作用,用作范围for

struct 定义类

class定义类

访问限定符

用struct定义类和用class定义类的区别

类的声明和定义分离

类的实例化

无成员变量的类的大小是1

this指针

this指针的内涵

this指针是不能修改的

this指针的地址

构造函数

定义:

限制:

代码样例

默认构造函数

析构函数

定义:~类名(){}

特性:


typeid(变量名).name()函数

作用:

这个函数可以查看某个变量的类型 

如图所示:

注意:引用不能够直接被auto所代替,他还需要把引用的符号显示出来才行,不然没人知道它是引用。


auto

auto的意义所在

auto就是一个能帮你自己推出这个变量的类型是什么,当然,auto应用在字母比它还要少的类型上没有意义,比如int。auto真正的作用,可以体现在类型长度较长的变量上。例如

#include<vector>
#include<string>

int main(){
    vector<string> v;
    vector<string>::iterator it  = v.begin();
    
    auto it = v.begin();
}

首先这个变量的类型是一个迭代器,其次可以明显的对比到它两的实用程度是auto更胜一筹的。

auto的注意事项

auto必须初始化,因为给了初始化才能根据初始化值来推导你这个变量究竟是什么类型

auto a;
auto b=1;

第一个,谁知道你的a是什么鬼东西;

第二个,编译器看到1,哦·~是整型,auto你给我记住你是整型。

这就是auto必须初始化

auto不能做函数的参数和返回值

void func(auto e){};
auto func(auto e){};

诸如上述两类,你调用还好,但万一我不调用呢???那它就又变成编译器不认识的东西了

所以,编译器为了不让它这么干,就把它给禁了。

有些编译器的更新程度比较新,已经支持了此类写法,但还没有被市场大量采用,所以在这里依旧是认为不做函式的返回值和参数的。

auto也不能做数组的声明(规定)

当然随着编译器的更新也说不准

auto的另一作用,用作范围for

它会将每次遍历的数值赋值给e,以此打印出来。自动判断结束,自动判断++

如果想要改变数组里的值的话,就要给e加上引用

原因是,e只是arr数组中每个值的拷贝而已,并不是直接把值给他,如果想要e改变就能带动arr[i]改变的话,就要变别名。


struct 定义类

结构体struct升级成了类,并且类名就是类型

struct Stack{
    int* a;
    int top;
    int capacity;

    void Init(){        //类里面还可以定义函数
        a = 0;
        top = 0;
        capacity = 0;
    }
    void Push(){}
    ……
};

int main(){
    Stack S;        //直接使用类
    S.init();       //用类调用函数
    S.Push(1);
    S.Push(2);
}

相比较于以前的调用来说,类使用轻松的多

struct stack s1;
StackInit(&s1);
StackPush(&s1,1);
StackPush(&s1,2);
StackPush(&s1,3);

class定义类

class className
{
    //由成员变量和成员函数构成
};   //一定要有分号的

访问限定符

public(公有) ,protected(保护),private(私有)

以后继承详讲,现在会认为(注意只是现在,保护和私有都是只能被类内所访问)

访问限定符影响的范围是,从这个访问限定符开始,直到遇到下一个访问限定符,或者下面没有访问限定符了,那就直接到结尾了

class Stack
{
private:
    int* a;
    int top;
    int capacity;

public:
    void Init() {
        a = 0 ;
        top = 0 ;
        capacity = 0 ;
    }
    void Push(int x) {
        ……
    }
};

如代码所示,private访问限定是到遇到public结束,意味着变量a,top,capacity都是私有的

下面这些函数就是公有的

用struct定义类和用class定义类的区别

不写访问限定符的时候struct的内容默认是公有的,而class的内容默认是私有的。 

类的声明和定义分离

1,类的成员变量跟类函数的声明放在.h文件中

2,类的函数定义放在.cpp文件中

        给函数定义的时候,需要给它标记它是属于哪个类的,不然编译器不认识

默认定义在类里面的一般就是当作内联函数,但是只是我们认为是内联,但它究竟是不是内联,还得看编译器,编译器看见是长的函数,它就绝对不会展开这个所谓的“内联”函数,如果是短的函数,就当作内联直接展开。

所以以后如果有设置内联函数展开的编译器,在调试的时候是不会调用这个函数的地址的,因为内联函数展开没有地址调用了

所以我们定义类的函数的时候,长函数一般声明定义分离,短函数不分离。

类的实例化

变量声明还是定义,要看有没有给这个变量开辟空间,声明没有开空间,定义是有了空间。

 

由上图可得,成员函数的空间并不在对象里面,对象的大小只跟成员变量有关,类对象计算的时候要考虑内存对齐。

(类的内存规则可参考博客:http://t.csdnimg.cn/Yio4U)

那成员函数究竟在哪

 通过汇编函数可以看到,我们的函数地址是一样的,且他们所在的空间是在07FF794A413F2h里面,调的是同一个函数。不同对象的成员函数是一样的。但是不同对象的成员变量是不一样的。

这个很简单理解,对象的成员变量是指属于那个对象的,而成员函数可以作为每个对象的功能实现,可以共同调用

无成员变量的类的大小是1

空类跟文件一样,文件不仅包含文件内容,还有文件属性,你可以没有文件内容,但你一定会有属性来存储。

空类的1只是标识的定义的对象存在过,这在后面的仿函数重载会大量用到

this指针

this指针的内涵

	void Print() {
		cout << _year << "-" << _month << "-" << _day << endl;
	}

    //其实它会变成,这是编译器会处理的

    //下面代码是不允许的,这里只是为了方便理解
    void Print(Data* this) {
		cout <<this-> _year << "-" << this->_month << "-" << this->_day << endl;
	}

而调用它的对象也会经过编译器的变化

d1.Print();
d2.Print();

//下面代码也是不允许的,这里仅仅是作为方便理解入手
d1.Print(&d1);
d2.Print(&d2);

所以回过头来看,C语言调用函数传的地址,跟C++默认传地址跟默认写入this形参,本质上是一样的。

但是我们不能将形参this相关的实参和形参明写出来的,即上述将编译器做出来的工作其实是错误的语法,即不能将编译器的活拿过来又重做一遍。

换种思路理解,有关this函数的形参和实参是已经存在了,只不过不显示出来,你再去加同样的东西,便会重复。

但通过后续演化,this可以在类里面,记住是类里面!!!才能使用

this指针是不能修改的

原因是:隐藏的形参(Data* this)真实全行是 (Data* const this

               是加了const的,const在星后面,是不能够修改this

            (const在星之前,是指这个指针指向的内容不能够被改变)

this指针的地址

this指针一般是存在栈帧里面的,但不同的编译器的存法不同。

以我的编译器为例,我的编译器是将this指针指向RCX寄存器里的,为了能够比内存更快访问。

构造函数

定义:

构造函数是特殊的成员函数,它的作用是初始化对象,而不是构造对象!

限制:

  1. 它的函数名与类名相同
  2. 它没有返回值,且不需要写void
  3. 对象实例化时编译器会自动调用对应的构造函数
  4. 构造函数可以重载

它的功能就是Init功能,只不过Init还要自己调用,而构造函数不用自己调用

代码样例

	//无参构造
    Data() {
		_year = 2000;
		_month = 1;
		_day = 1;
	}

    //有参构造
	Data(int year, int month, int day) {
		_year = year;
		_month = month;
		_day = day;
	}
	Data d1;	            //自动调用无参构造
	d1.Print();

	Data d2(2024, 5, 20);    //自动调用有参构造
	d2.Print();

无参构造的时候不要加括号!!!

当然也可以用缺省参数,但不能跟无参构造的初始值一样

下面便来看看错误的做法

	Data() {
		_year = 2000;
		_month = 1;
		_day = 1;
	}

	Data(int year = 2000, int month=1, int day=1) {
		_year = year;
		_month = month;
		_day = day;
	}

(这里说出“认为有多个默认构造”,为什么这么说,且请你继续看下去)

所以为了解决这个问题,将无参与全省合二为一就可以了。

	/*Data() {
		_year = 2000;
		_month = 1;
		_day = 1;
	}*/

	Data(int year = 2000, int month=1, int day=1) {
		_year = year;
		_month = month;
		_day = day;
	}

默认构造函数

当我们没有写构造函数的时候,编译器会自动帮我们生成一个默认的构造函数 

	//Data(int year = 2000, int month=1, int day=1) {
	//	_year = year;
	//	_month = month;
	//	_day = day;
	//}

现在将其都注释掉,运行函数,函数依旧正常运行

我们可以看见我们打印出来的东西是随机值,那么他是不是啥都不干呢

答案不是的

默认生成构造函数:内置类型不做处理,自定义类型会去调用它的默认构造 。

因为Data类里基本上是内置类型,所以一般不会做初始化

(注意:这个要看编译器的版本,因为这个通常来讲不初始化才会更加规范,一些高级的编译器,如19版,就是当有自定义类型的时候,内置类型会初始化当没有自定义类型,只有内置类型的时候,它又不会初始化,就显得有点半生不熟)

内置类型一般是像 int,double, char等自带,此外指针也是内置类型,无论你是自定义类的指针,还是其它类型的指针,指针就是指针,他就是内置类型。

所以当我们用默认构造函数的时候,且有自定义类型和内置类型的时候,会觉得太乱了,要么你就全初始化嘛,要么你就都不要给我初始化,搞成一半一半的

所以后面就对这种现象做出改变:声明给缺省值,这就解决了内置类型初始化的问题了

这种依旧是声明,不是定义,因为没有开空间,且这些1是缺省值。

即支持声明时给缺省值

声明给缺省值一般是没有构造函数的时候,但当我们有写了构造函数呢,即声明缺省值跟无参构造赋值谁的优先级高呢?

class Date{
public:
	Data() {
		_year = 2000;
		_month = 1;
		_day = 1;
	}
private:
	//这是声明
	int _year = 1;
	int _month = 1;
	int _day = 1;
};

答案显而易见,是构造函数的优先级高于声明给缺省参数值,缺省的意义就是当我缺了我才去用,我都不缺,我写什么

如果类中没有显示定义的构造函数,则C++编译器会自动生成一个无参的默认构造函数,一旦用户显示定义了构造函数,不管有参还是无参,编译器都不会再生成默认构造函数。

class Date(
public:
    Data(int year, int month, int day) {
		_year = year;
		_month = month;
		_day = day;
	}

	void Print() {
		cout << this->_year << "-" <<this-> _month << "-" <<this-> _day << endl;
	}
private:
    int _year = 1;
    int _month = 1;
    int _day = 1;
);

int main(){
    Date d1;
    d1.Print();
}

这串代码的答案又会是什么?报错,显示出没有合适的默认构造函数使用

我们需要记住的是只要我们没有写构造函数的时候,就有默认构造函数,因为我们定义的d1类是无参的,所以我们的没有全缺省的有参构造,对不上,且当我们去找默认构造的时候,发现没有,因为我们已经有有参构造了。

所以回过头来,我们可以认为,

无参构造也可以叫默认构造,

全缺省的有参构造也可以叫默认构造

三者不能同时存在,否则会出现调用歧义!!!。

析构函数

定义:~类名(){}

与构造函数相反,完成对对象中资源的清理。

简单来说,就是没有用类时的C语言的destory函数。

特性:

  1. 出了函数域,析构函数会被自动调用
  2. 无参数无返回值,不能够重载

析构函数处理的是在栈帧上的成员变量所指向的内容,这个内容是不在栈帧上的,一般是属于动态开辟的空间。

默认生成的析构函数 对内置类型不做处理,对自定义类型会去调用它自己的析构函数,这点与构造函数同理。

但默认析构函数不会去清空你自己的成员变量所指向的内容,得自己手动构造的析构才敢。

以上便是此次博客的学习内容,感谢各位大佬的浏览,如果博客有错误,还请各位大佬指出斧正,谢谢!

  • 28
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值