c类中如果没有使用private关键,c类中小学教师
Yyds干货库存
@toc
我们已经初步实现了类的内容,但都是基础的。今天我们就来分享一些细节,整理一下我的知识,比较难,所以这个博客我可能写的不是很好。如果你有任何问题,请在评论区留言。我看到了会马上回复。如果我什么都不知道,我会帮你查资料。请理解。
该类的六个默认成员函数
这个博客主要是和大家分享成员函数(方法)。里面有很多内容。让我一个一个来。让我们看一些例子作为今天的开始。
请问这门课有什么内容?
阶级人士
};
这不就是一堂空课吗?里面什么都没有。如果你这么认为,那就有点简单了。是的,我们看到里面什么都没有,但实际上编译器会自动生成六个默认的成员函数,存在于这个类中。至于怎么验证,不用担心单机。
我们先来看看这六个成员函数,不过我们只详细讲其中的四个,另外两个不是很重要。就简单说一下吧。
构造函数完成对象的初始化。它不是构造函数。
析构函数完成资源清理。
必须调用复制函数来构造对象副本。
赋值重载重定义运算符
构造函数,也称为构造函数,帮助我们在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;
}
每次我们实例化一个对象,我们必须设置它。这是不是有点太麻烦了?有时我们可能会忘记初始化它。于是聪明的程序员就想,是不是可以让编译器在对象实例化的时候帮我们自动初始化,这样就不用说忘记了。于是一个特殊的函数——构造函数出现了。
构造函数的特征
现在我们可以正式知道构造函数了,它有以下特征。
没有返回值。记住返回值是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;
}
为什么,这是戒酒协会
这就要解决上面遗留的问题了。说实话,这是语法规定的,我很难理解,但是我们可以通过现象来理解一些东西。
先解编译器生成的。这里解决不了,也不要看这里的现象。记住就好。
然后开始解自己写的无参数构造函数,我们来看现象。
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;
}
这样,我们也可以得到一个结果。我们不需要为自定义类型编写析构函数,但是需要为内置类型清理资源,避免内存泄漏。
创建对象时,能否创建与对象相同的新对象?复制构造是构造函数的一种,也是我们以后上课写作的重点内容。我们需要了解它。
构造函数:只有一个参数,是对这个类类型对象的引用(通常用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指向的内容不变,所以我们把它免费放了两次,程序就会中断。
复制结构的特征
我们认识到了值copy,现在可以说copy构造了,也是编译器默认生成的构造函数。函数名与类名相同。
复制构造函数是构造函数的重载形式。
复制构造函数的参数只有一个,而且必须通过引用传递,这样会导致无限的递归调用。
先不说第二个话题,最后分享一下。
生成默认拷贝构造
让我们首先来看看默认的复制构造是如何工作的,看看什么时候我们需要编写自己的复制构造。
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可以避免这个错误,因为它不编译,但是可以很快发现问题。
为什么要用参考文献?
你找到了最重要的东西。首先你要记住,要复制字体color=red自定义类型,必须先调用构造函数/font。如果你写一个公共参数,你需要复制它。编译器开始寻找构造函数,找到构造函数发现需要复制。有一个死循环,所以我们必须使用别名来避免复制。
内置类型的构造函数呢?
这个我们已经详细讲过了,这里有个结论。如果你的类中没有这种类似的指向同一个空格的属性,用默认的也可以,但是如果有,就需要自己写了。至于这个涉及到深浅临摹的知识怎么写,这里就不说了。这条规则适用于大多数情况。
自定义类型的构造函数呢?
这个就不放动画了。像构造函数和析构函数一样,它寻找自定义类型的构造函数。
赋值运算符重载
本来想分两部分写的。这也是一大内容。我们可以为赋值操作符创建自己的实现规则。让我们先来看看什么是赋值运算符重载。
引入了运算符重载来增强代码的可读性。运算符重载是一个具有特殊函数名的函数,它也有自己的返回值类型、函数名和参数列表。它的返回值类型和参数列表与普通函数相似。
原型:返回值类型运算符运算符(参数列表)
为什么赋值运算符会重载?
我们先来看一个应用。
为什么会报错?我只是想让他们比较一下。我怎么了?但是编译器不允许。今天,我必须让它允许我。这就是赋值运算符重载出现的原因。
上课日期
公共:
日期(整数年=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函数来获取这些属性的值,另一种是使用友元,但是这种方法破坏了封装,不推荐。
在类中写入运算符重载。
我们还不如直接把这个函数写在类里。简单快捷,避免破包。
上课日期
公共:
日期(整数年=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;
}
重载运算符=
本来想和大家分享一个约会类的,但是如果是此时此地写的话,至少需要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的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。