c++初级,c++类怎么理解
Yyds干货库存
在写作之前,我们已经初步认识到类的内容,但那些是基本的。今天就分享一些细节,整理一下我的知识,比较难。所以,这个博客我可能写的不是很好。如果你有任何问题,请在评论区留言。我看到了会马上回复。如果我什么都不知道,我会帮你查资料。请理解。
这个博客主要是和大家分享成员函数(方法)。里面有很多内容。让我一个一个来。让我们看一些例子作为今天的开始。
请问这门课有什么内容?
阶级人士
{
};
这不就是一堂空课吗?里面什么都没有。如果你这么认为,那就有点简单了。是的,我们看到里面什么都没有,但实际上编译器会自动生成六个默认的成员函数,存在于这个类中。至于怎么验证,不用担心单机。
我们先来看看这六个成员函数,不过我们只详细讲其中的四个,另外两个不是很重要。就简单说一下吧。
构造函数完成对象的初始化。完成资源清理的不是构造函数的析构函数。构造函数的副本必须调用复制函数赋值重载重定义运算符。
构造函数,也称为构造函数,帮助我们在C\记住,它们帮助我实例化对象。我们先来看看这个。
阶级人士
{
公共:
作废打印()
{
Cout 我的名字是 _name ,我今年 _age 岁 endl
}
空集合(const char* name,const int age)
{
strcpy(_name,name);
_age=年龄;
}
私人:
int _ age
char _ name[20];
};
int main()
{
Person per1
1.per1。集(《张三》,18);//每次都设置
per1。print();
返回0;
}我们每次实例化一个对象,都要设置它。是不是有点太麻烦了?有时候我们可能会忘记初始化它,于是聪明的程序员就想,能否在对象实例化的时候,让编译器自动帮我们初始化,这样就不谈忘记了,于是一个特殊的函数——构造函数就出现了。
构造函数的特征现在我们可以正式认识构造函数了。构造函数具有以下特征。
没有返回值。记住返回值是yes还是no。void函数的名称与类名相同。当重载对象被实例化时,编译器自动调用相应的构造函数。我们先优化一下前面的代码,写一个构造函数。
阶级人士
{
公共:
作废打印()
{
Cout 我的名字是 _name ,我今年 _age 岁 endl
}
//带两个参数的构造函数(编译器的这个不算)
人员(常数字符*姓名,常数年龄)
{
strcpy(_name,name);
_age=年龄;
}
私人:
int _ age
char _ name[20];
};
int main()
{
Person per1(《张三》,18);
per1。print();
返回0;
}
默认构造函数是一种特殊的构造函数。需要先看下面,只是初步了解,以后再说。
编译器自动生成不带参数的构造函数。所有默认构造函数都是由编译器自动生成的。总的来说,构造者可以说说上面的,但是对我们来说远远不够。我们需要明白一些事情。在第一段代码中,我想问一下类中是否有构造函数。这个问题我一开始就回答了。现有的编译器会自动生成构造函数。
我们来验证一下。如果我们在类中写自己的构造函数,会发生什么?
A级
{
公共:
A(int a)
{
}
};
int main()
{
A _ a
返回0;
}
我们开始疑惑,为什么没有写构造函数就报错了。我们一眼就能看出,我们在实例化对象时没有传递参数,这就是问题所在。从这里可以看出,编译器在实例化对象时会自动调用匹配的构造函数,也就是说,实例化对象时构造函数肯定会参与。
但是看看下面的代码,为什么不报错?原因是编译器自动生成无参数构造函数。至于它的结构,没必要知道。
B类
{
};
int main()
{
B b
返回0;
}
由此可以得出一个结论。如果我们不写构造函数,编译器会自动生成一个,但是如果我们写了,编译器不会。至于什么时候写,什么时候不写,先放在这里吧。在这里拓展还是有点困难。让我们把它放在最后。
无参数构造函数。自己写的无参数构造函数也是默认构造函数。就说到这里吧。
比较简单。至于代码里的问题,别急,我最后再说。
A级
{
公共:
答()
{
a=0;
}
作废打印()
{
Cout“无参数构造函数”endl
cout a= a endl
}
公共:
int a;
};
int main()
{
A a//为什么不写一个A()?
a.print();
返回0;
}
默认构造函数是最后一个。总的来说,我们对这里的默认构造函数了解很多,所以更容易理解。我们来看现象。
A级
{
公共:
//所有默认值
a(整数a=10)
{
_ a=a
}
作废打印()
{
Cout“无参数构造函数”endl
cout a= _ a endl
}
公共:
int _ a;
};
int main()
{
A a//为什么不写一个A()?
a.print();
返回0;
}
哈,这是AA,要解决上面遗留的问题。说实话,这是语法规定的,我很难理解,但是我们可以通过现象来理解一些东西。
先解编译器生成的。这里解决不了,也不要看这里的现象。记住就好。
然后开始解自己写的无参数构造函数,我们来看现象。
A级
{
公共:
答()
{
}
作废打印()
{
Cout你好,世界’endl;
}
};
int main()
{
a a();
a.print();
返回0;
}
也就是说编译器找不到匹配的构造函数,或者不会报错。
最后一个是完整的默认构造函数。
A级
{
公共:
//所有默认值
a(整数a=10)
{
_ a=a
}
答()
{
}
作废打印()
{
Cout“无参数构造函数”endl
cout a= _ a endl
}
公共:
int _ a;
};
int main()
{
a a();
a.print();
返回0;
}
这个错误和上面一样,只是我们不理解这个。默认不允许我们这么做吗?可以,但是这里不允许。
你可能看我废话了半天。是的,我解释不清楚,但我可以知道一个这样的问题。
如果一个类中有一个不带参数的构造函数,同时也有一个完全默认的构造函数,我们用A A();实例化一个对象,我想问一下编译器会用哪个构造函数,但是编译器不知道,干脆放弃这个用法,就结束了。
我想和你谈谈默认构造函数的优先级。我不知道我的题名是否准确,也不知道这个知识点叫什么。来,我给你描述一下。
如果一个类中有一个无参数的构造函数,同时也有一个完全默认的构造函数,我们用一个A;调用哪个构造函数?在这里用现象来得到答案。
A级
{
公共:
//所有默认值
a(整数a=10)
{
Cout all default endl
}
//没有参数
答()
{
Cout no reference endl
}
};
int main()
{
A a
返回0;
}
不好意思,这个会有误差,所以谈不上优先级。大家要记住这一点。无参数构造函数和全默认构造函数不能同时存在。我建议写全默认构造函数。
我们前面提到了默认构造函数的作用。构造函数是用来为我们初始化的。但是这个初始化也有一个很大的问题。这里C\之前有一个很大的缺陷,直到C\ 11才弥补了一部分。
不改内置C型的特点,很让人苦恼。使用默认构造时忽略内置类型有点不可接受。我们一眼就能看出来。
A级
{
公共:
答()
{
}
私人:
int _ a;
double _ d;
};
int main()
{
A a
返回0;
}
C 11填这个坑太大了。反正我对这个问题破口大骂。还好C\ 11填了这个坑,但是这种方法也会给初学者造成很大的困扰。先说方法。
A级
{
公共:
答()
{
}
私人:
int _ a=0;//在此声明
double _ d=0.0
};
给这个初学者一个误解,int _ a=0;是的,但是打开空间是在实例化一个对象的时候,这只是一个声明,类似于默认函数。
初始化自定义类型。上面的标题也不准确。可以说,对于自定义类型,编译器会调用自定义类的默认构造函数来帮助初始化。
A级
{
公共:
答()
{
Cout 协助初始化 endl
}
私人:
int _ a;
double _ d;
};
B类
{
公共:
乙()
{
}
A _ aa
};
int main()
{
B b
返回0;
}
我们需要查看内部的结果,并再次调试。
有些人可能会看到_aa的内容也没有初始化。这是因为我们没有在类A中写默认初始化,我来重写类A,调试一下。仅此而已。
A级
{
公共:
答()
{
//写到这里。
_ a=0;
_ d=0.0
}
私人:
int _ a;
double _ d;
};
从这里可以看出,我们需要初始化类中的内置类型,而不是自定义类型。
说到这里,我们现在需要做一个结论。我们学过构造函数,知道默认构造函数,了解构造函数的作用,这些都是相当难的。
析构函数如果构造函数是为了初始化,那么析构函数就是为了资源清理,这对于一些比较忘记的程序员来说是个福音。比如我们用malloc开辟一个空间,有时候很容易忘记free,会造成内存泄露。这样会造成一些问题。但是析构函数可以在对象的生命周期结束后自动调用这个析构函数。我们只需要在这个析构函数中自由。
析构函数的特点我们先来看看析构函数的特点,这是我们的基础。
析构函数名称前面有字符~。没有参数,没有返回值。一个类有且只有一个析构函数。如果没有明确定义,系统将自动生成默认的析构函数。在对象生命周期的末尾,C编译器系统自动调用析构函数。自动编译器调用我们先来看一个例子。
A级
{
公共:
a(整数上限=4)
{
int * arr=(int *)malloc(sizeof(int)* cap);
断言(arr);
a=arr
memset(arr,0,sizeof(int)* cap);
_ cap=cap
}
~A()
{
_ cap=0;
免费(a);
a=nullptr
}
私人:
int * a;
int _ cap
};
int main()
{
A a
返回0;
}从上图我们可以知道,对象A的声明期结束后,编译器会自动调用析构函数完成资源清理。
默认析构函数我们需要看看默认析构函数发生了什么,这可以帮助我们更高效地编写代码。
析构函数会清理内置类型的资源吗?抱歉,它不能帮助我们清理内置类型。
A级
{
公共:
a(整数上限=4)
{
int * arr=(int *)malloc(sizeof(int)* cap);
断言(arr);
a=arr
memset(arr,0,sizeof(int)* cap);
_ cap=cap
}
私人:
int * a;
int _ cap
};
析构函数可以清理自定义类型吗?这是可能的,但是需要调用自定义类型的析构函数,这与默认构造函数的初始化是一样的。
将调用自定义类型的析构函数。
A级
{
公共:
~A()
{
自定义类型“endl”的“Cout”析构函数;
}
};
B类
{
私人:
A _ aa
};
int main()
{
B b
返回0;
}
我们还可以看看如何调用析构函数。
A级
{
公共:
a(整数上限=4)
{
int * arr=(int *)malloc(sizeof(int)* cap);
断言(arr);
a=arr
memset(arr,0,sizeof(int)* cap);
_ cap=cap
}
~A()
{
_ cap=0;
免费(a);
a=nullptr
}
私人:
int * a;
int _ cap
};
B类
{
公共:
乙()
{
}
私人:
A _ aa
};
int main()
{
B b
返回0;
}总结这样,我们也可以得出一个结果。我们不需要为自定义类型编写析构函数,需要为内置类型清理资源,避免内存泄漏。
复制构造创建对象时,能否创建一个与对象相同的新对象?复制构造是构造函数的一种,也是我们以后上课写作的重点内容。我们需要了解它。
构造函数(Constructor):只有一个参数,这个参数是对这个类的一个对象(通常用const修饰)的引用,当用这个类的一个现有对象创建一个新对象时,由编译器自动调用。
值的拷贝我们刚学函数的时候,大部分时候会给函数传入参数,也就是编译器会开辟另一个空间,把要传入传出的内容的拷贝放到这个空间里。这是价值观的简单复制。
我们真的需要好好看看这个价值副本,我们发现它们的地址不一样。
void func(int b)
{
cout a b endl
}
int main()
{
int a=10
func(a);
cout a a endl
返回0;
}
对于一些简单的类型,这个副本就可以了,但现在我想给你看这个。
void函数(int* pb)
{
免费(Pb);
}
int main()
{
int * arr=nullptr
arr=(int *)malloc(sizeof(int)* 4);
func(arr);
免费(arr);
返回0;
}
我们会发现一个问题。对于某些类型,简单的值复制根本不够。为什么会报告上述错误?原因是我们把数组名作为参数,编译器简单的把它作为指针复制到pb,但是pb指向的内容不变,所以我们把它免费放了两次,程序就会中断。
复制构造的特点我们认可价值复制,现在可以说复制构造了。复制构造也是编译器默认生成的构造函数,其名称与类名相同。
复制构造函数是构造函数的重载形式。复制构造函数的参数只有一个,而且必须通过引用传递,这样会导致无限的递归调用。先不说第二个话题,最后分享一下。
默认副本构造我们先来看看默认副本构造是怎么工作的,看看什么时候需要自己写副本构造。
A级
{
公共:
A(int a=0,double d=0.0)
{
_ a=a
_ d=d
}
~A()
{
_ a=0;
_ d=0.0
}
公共:
int _ a;
double _ d;
};
int main()
{
A _aa(1,3.0);
cout aa。_a= _aa。_ a;
cout aa。_d= _aa。_ d endl
a _ bb(_ aa);
cout _bb。_a= _bb。_ a;
cout _bb。_d= _bb。_ d endl
返回0;
}
这个构造可以说是一样的,所以我们不用担心编译器这次不会忽略内置类型。这很好,但也有问题。下面来看看。
默认构造函数是值副本吗?这个问题很严重。我们应该知道,我们的对象将在生命周期结束时调用析构函数。如果这种情况发生两次,我想大家都会诅咒的。
让我们拭目以待
A级
{
公共:
a(整数上限=4)
{
int * pa=(int *)malloc(sizeof(int)* cap);
断言(pa);
_ array=pa
}
~A()
{
free(_ array);
_ array=nullptr
}
公共:
int * _ array
};
无效函数(A _bb)
{
cout _bb。_ array endl
}
int main()
{
A _ aa
func(_ aa);
cout _aa。_ array endl
返回0;
}
这意味着默认情况下只生成一个简单的值副本,意味着后面的代码会被中断,同一个空间会被多次释放。
int main()
{
A _ aa
a _ bb(_ aa);
返回0;
}
手写构造函数分享了这么多,好像我们还没有一个手写的构造函数。这张是常见的,但是里面有很多细节。
A级
{
公共:
A(int a=0,double d=0.0)
{
_ a=a
_ d=d
}
A(常数A)
{
_ a=a. _ a
_ d=a. _ d
}
私人:
int _ a;
double _ d;
};
我们开始挖掘细节。
为什么用const装修好?我们不需要用const来修饰,但是有时候会写这样的代码。
A(常数A)
{
a._ a=_ a//倒着写
_ d=a. _ d
}
修改const可以避免这个错误,因为它不编译,但是可以很快发现问题。
为什么用引用找到最重要的东西?首先要记住,要复制自定义类型,必须先调用构造函数。如果写的是普通的参考文献,也需要抄。编译器开始寻找构造函数,找到构造函数发现需要复制,寻找构造函数.有一个无限循环,所以我们必须使用别名来避免复制。
如何处理构造函数的内置类型?我们已经详细讨论过这个问题了。这里有一个结论。如果你的类中没有这种类似的属性指向同一个空格,用默认的也可以,但是如果有,就需要自己写了。至于怎么写涉及到明暗抄写的知识,这里就不说了。这条规则适用于大多数情况。
构造函数对自定义类型做了什么?我就不展示图表了。像构造函数和析构函数一样,构造函数寻找自己的构造函数。
赋值运算符重载本来是分两部分写的,这也是很大的内容。我们可以为赋值操作符创建自己的实现规则。我们先来看看什么是赋值运算符重载。
引入了运算符重载来增强代码的可读性。运算符重载是一个具有特殊函数名的函数,它也有自己的返回值类型、函数名和参数列表。它的返回值类型和参数列表与普通函数相似。
原型:返回值类型运算符运算符(参数列表)
为什么赋值运算符会重载?我们先来看一个案例。
为什么会报错?我只是想让他们比较一下。我怎么了?但是编译器不允许。今天,我必须让它允许我。这就是赋值运算符重载出现的原因。
上课日期
{
公共:
日期(整数年=1900,整数月=1,整数日=1)
{
_year=年份;
_month=月;
_day=天;
}
私人:
int _ year
int _ month
int _ day
};
int main()
{
日期d1(2022,5,18);
日期d2(2022,5,18);
如果(d1==d2)
{
cout == endl
}
返回0;
}
赋值运算符有很多重载,现在来看看如何使其合理。
我们来看看这个函数。现在有一个问题。我们无法获得该类的属性。它被封装了。先把属性改成public,再解决这个问题。
布尔运算符==(日期d1,日期d2)
{
//在这里,年、月、日是相等的。
返回d1。_year==d2。_年
d1。_month==d2。_月
d1。_day==d2。_ day
}这里就可以了,我们调用这个函数吧
int main()
{
日期d1(2022,5,18);
日期d2(2022,5,18);
if(运算符==(d1,d2))
{
cout == endl
}
返回0;
}
我们可能会想,我可以随便取个函数名就把这个函数的函数写出来,而且还是那么花哨,但是你写的函数可以这样调用吗?但是我的也可以。
如果(d1==d2)
{
cout == endl
}
这就是重载这个运算符的魅力所在。现在需要完善这个函数,参考一下。不需要开辟空间。用const修饰,避免被意外修改。
布尔运算符==(常数日期d1,常数日期d2)
{
//在这里,年、月、日是相等的。
返回d1。_year==d2。_年
d1。_month==d2。_月
d1。_day==d2。_ day
}针对无法获取属性的问题,我给出两种解决方案。一种是在类中写一些GET函数来获取这些属性的值,另一种是使用friends。但是,这种方法会破坏封装,不推荐使用。
在类中写入运算符重载。我们还不如直接把这个函数写在类里。简单快捷,避免破包。
上课日期
{
公共:
日期(整数年=1900,整数月=1,整数日=1)
{
_year=年份;
_month=月;
_day=天;
}
布尔运算符==(常数日期d1,常数日期d2)
{
//在这里,年、月、日是相等的。
返回d1。_year==d2。_年
d1。_month==d2。_月
d1。_day==d2。_ day
}
公共:
int _ year
int _ month
int _ day
};
你为什么报告这个错误?参数不是很对吗?我们之前说过,编译器默认会增加一个这个指针类型的参数,而且==是两个操作数,所以参数太多了。我们可以减少一个参数。
boo operator==(const date d)//默认添加一个this指针
{
//在这里,年、月、日是相等的。
return _ year==d. _ year
_ month==d. _ month
_ day==d. _ day
}
这个函数的调用就变成这样了。
int main()
{
日期d1(2022,5,18);
日期d2(2022,5,18);
If (d1==d2) //d1==d2变成d1.operator==(d2)默认情况下
{
cout == endl
}
返回0;
}
代码里你说变成d1.operator==(d2),所以变成这样?你能证明什么?这里用对象的地址来证明。
上课日期
{
公共:
日期(整数年=1900,整数月=1,整数日=1)
{
_year=年份;
_month=月;
_day=天;
}
布尔运算符==(常数日期d)
{
cout“this”this endl;
返回true
}
公共:
int _ year
int _ month
int _ day
};
int main()
{
日期d1(2022,5,18);
日期d2(2022,5,18);
cout d1 d1 endl
cout d2 d2 endl
如果(d1==d2)
{
}
返回0;
}
重载运算符=本来是想和大家分享一个date类的,但是如果此时此地写,至少需要5000字。我把它单独放在一个博客上,作为这几天我们学习课的小总结。在这节约会课上,你会发现我们上次讲过的所有知识点。这是一道小菜。这个很简单,目的是引出以下知识点。
日期运算符=(常数日期d)
{
如果(这个!=d)
{
_ year=d. _ year
_ month=d. _ month
_ day=d. _ day
}
返回* this
}
先称之为。
int main()
{
日期d1(2022,10,18);
日期D2;
d2=d1
d2。print();
返回0;
}
D2=d1细心的朋友可能会发现,我们写的是d2=d1我不想给d2赋值,而是想在这里重点关注它。我们通过调试来看看。
这个电话是话务员超负荷。
int main()
{
日期d1(2022,10,18);
日期D2;
d2=d1
d2。print();
返回0;
}
日期d2=d1该调用是复制构造,而不是操作符重载。
int main()
{
日期d1(2022,10,18);
日期d2=d1
d2。print();
返回0;
}
从这里可以得出下一个结论。如果我们在定义变量并初始化它们的时候调用copy构造,如果在赋值的时候已经定义了两个变量,我们称之为操作符重载。
郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。