CPP类与对象总结(详细易懂)
Cpp中的类与对象总结。
很多人对C语言与cpp语言的区别的解释为:c语言是面向过程的,而c++是面向对象的。当然也有大佬反对这句话,但不管怎样,都可以看出面向对象是c++的一大重要特征,甚至很多c++课程将面向对象的课程称之为“c++”核心编程。关于这一方面的总结csdn上也有很多,我相信我的总结并不会比他们详细。但我希望以一个学习者的角度来解释这一看似复杂高深的内容,可以让大家获益。很多内容均来自个人的学习笔记与课程笔记。
类与对象概述:
提到类,首先想到“class”,class作为类的声明,有类就有它。然后就是“属性”与“方法”,这二者几乎构成了类的主题。简单一点理解,属性相当于名词,而方法相当于动词,当然详情请看下文。接下来可能就是“权限”了,涉及到类外访问,及子类访问的问题。再然后就是**“构造函数” “析构函数”**,这涉及初始化,深浅拷贝及清空堆区等问题。也是比较复杂的一块内容。最后就是所有面向对象的编程语言都具有的三块“封装”、“继承”与“多态”。
下面我们来一一分析一波。
类的基本实现方法:
请看代码中的注释:(public可以先不用管,详见下文权限内容)
#include<iostream>
using namespace std;
class zan {
public:
//定义字符串,zan类下的观众属性,为字符串赋值
string viewer = "读者老爷";
//定义字符串,zan类下的点赞属性,为字符串赋值
string dianZan = "请点一个宝贵的赞";
//定义thanks函数进行输出操作
void thanks() {
cout << viewer << "," << dianZan << endl;
}
};
//测试函数,实例化对象并调用thanks方法,等……
void thank() {
zan your_zan;
your_zan.thanks();
cout << "谢谢浏览。" << endl;
}
//在主函数中调用函数
int main() {
//^_^//
thank();
system("pause");
return 0;
}
好吧,我承认上面的代码很皮,但大家可以通过它具体了解一下cpp中对对象相关操作的基本内容。一个类的实现,可以说基本上就是这样的了。
权限
权限总共有三种,上文大家看到的“public”就是一种,还有“private”和“protected”,权限的字面意思应该很好理解,如果我们把这篇文章定义为一个类,这篇文章发表了,所有人都可以看,这就是public。如果这篇文章没有发布,储存在我这台带有开机密码的电脑里,那么这就是private。protected与private相似,唯一的区别就是其继承的子类(下文将具体讲解)是否可以访问的问题,private子类不可访问,protected子类可以访问。在类中若无权限声明,将默认为private。(注:结构体的权限默认为公开)。所有上面的代码中如果没有public,在下文的thanks函数中便不可以访问zan之中的thank方法。也许在一开始我们认为一个类中全部使用public方法可能是最方便的了。但其实,在真正的开发中,我们一般将属性的权限都设为private。其实,这也不难理解,一旦超越权限,后果将会不堪设想。。
//输入两个立方体的各边,判断是否为同一立方体。利用表面积与体积相等判断。
#include<iostream>
using namespace std;
class Cube
{
//公共权限下的 设置长宽高 与 获取长宽高 的方法。
//注意长宽高在下面是私有属性,在类外访问不到。
public:
void setl(int l) {
m_l = l;
}
int getl() {
return m_l;
}
void setw(int w) {
m_w = w;
}
int getw() {
return m_w;
}
void seth(int h) {
m_h = h;
}
int geth() {
return m_h;
}
//以引用的形式传入另一个cube类,并判断两边是否相等。
bool adj(Cube &a) {
if (m_l == a.m_l&&m_w == a.m_w&&m_h==a.m_h) {
return true;
}
else
{
return false;
}
}
//下面是上文使用过的私有属性,长宽高。
private:
int m_l;
int m_w;
int m_h;
};
//这个与上面的类中的bool adj功能相同。(两种不一样的方法)
bool adjust(Cube &a,Cube &b)
{
if (a.getl() == b.getl() && a.getw() == b.getw() && a.geth() == b.geth())
{
return true;
}
else
{
return false;
}
}
int main() {
//类的实例化,及参数赋值。
Cube c1;
c1.setl(10);
c1.setw(10);
c1.seth(10);
Cube c2;
c2.setl(10);
c2.setw(10);
c2.seth(10);
int l = c1.getl();
int w = c1.getw();
int h = c1.geth();
cout << "表面积为: " << 2 * l * w + 2 * l * h + 2 * w * h << endl;
cout << "体积为: " << l * h * w << endl;
bool ret = adjust(c1,c2);
if (ret)
{
cout << "二者为同一立方体" << endl;
}
else
{
cout << "二者不为同一立方体" << endl;
}
bool ret1=c1.adj(c2);
if (ret1)
{
cout << "在对象函数中输出,二者为同一立方体" << endl;
}
else
{
cout << "在对象函数中输出,二者不为同一立方体" << endl;
}
system("pause");
return 0;
}
上面的代码,主要是为了方便大家理解权限,代码过于冗长(主要也是为了进一步加深大家对类的理解,特别是上文bool adjust 在类内与内外的不同实现方法)。我们看到了属性与方法的不同权限,这也是为了规范,即在类外我们一般不可以直接动属性,而必须通过public下的特定方法来实现。这样在上千行代码中你便少了一种出现bug的可能,你也失去了一次秃头的机会。
我们还是就上面这一段代码本身来讲解一下吧。在函数传入的参数中,我们可以传入一个类。因为是形参,所有我们一般以引用的方式传入(关于形参与引用可以自行csdn)。这个函数可以在类内也可以实现,类外也可以实现,但传入的参数是有区别的。这也很好理解。因为类内的函数本来就可以访问这个类中的所有属性与方法。
默认函数
- 创建一个类,c++编译器会给每个类都添加至少三个函数
- 默认构造(空实现)
- 析构函数(空实现)
- 拷贝函数(值拷贝)
- 如果我们写了有参构造函数,编译器不会再提供默认构造,但依然提供拷贝构造。
- 如果提供了拷贝构造函数,编译器将不再添加其他函数。
构造函数
构造函数:主要作用在于创建对象时为对象的成员属性赋值,构造函数由编译器自动调用,无须手动调用。
构造函数语法(初始化)
构造函数语法:类名(){}
1.构造函数,没有返回值也不写void
2.函数名称与类名相同
3.构造函数可以有参数,因此可以发生重载
4.程序在调用对象时候会自动调用构造,无须手动调用而且只套调用一次
构造函数的分类及调用两种分类方式:
-
按参数分为:有参构造,无参构造(默认)。
-
按类型分为:普通构造,拷贝构造。
-
三种调用方式:括号法,显示法,隐式转换法。
//在person类中
//一般我们用拷贝函数实现初始化
person(int a){}//有参构造
person(){}//无参构造
person(const person &p){}//拷贝构造
/*++++++++++++++调用构造函数++++++++++++++++++*/
//与实例化一起进行
//===括号法===
person p1;//默认构造
//person p1();不正确,编译器会认为是声明
person p2(10);//有参构造
person p3(p2);//拷贝构造
//===显示法===
person p1;//默认构造
person p2 = person(10);//有参构造
person(10);//匿名对象,执行完后立即释放,因为并没有实例来接收,一般不建议使用。
person p3 = person(p2);//拷贝构造
//注意不要利用拷贝构造函数初始化匿名对象
/*person(p3);编译器会认为重定义
因为系统认为person(p3)==person p3*/
//===隐式转换法===
person p4 = 10;/*相当于*/person p4 = person(10);
person p5 = p4;//拷贝构造
上面的内容还是很详细的,其实我们只要多练习,并能熟悉自己常用的就可以了。我们真正使用构造函数主要还是为了初始化某些属性,已经让某些功能更好地实现。
构造函数还可以实现初始化列表:如下
//法1
class Person
{
Person():m_A(10),m_B(20),m_C(30)
{
}
int m_A;
int m_B;
int m_C;
};
//法2
class Person
{
Person(int a,int b,int c) :m_A(a),m_B(b),m_C(c)
{
}
int m_A;
int m_B;
int m_C;
};
析构函数
析构函数:主要作用在于对象销毁前系统自动调用,执行一些清理工作。
析构函数语法(清理)
析构函数语法:~类名(){}
1.析构函数,没有返回值也不写void
2.函数名称与类名相同在名称前加上符号 ~
3.析构函数不可以有参数,因此不可以发生重载
4.程序在对象销毁前会自动调用析构,无须手动调用而且只会调用一次
析构函数的实现基本上与构造函数相似,但不能传入参数,所有更简洁吧。这里不再过多示例,
~person(){ /*注意这是person类中的一个析构函数*/}
这些函数执行的顺序,大家可以在构造函数与析构函数中加入 cout<<""<<endl; 语句自行验证其执行顺序(一般来说构造函数最先调用,析构函数最后调用)还是加入几行代码演示一下吧,大家可自行运行一下。
//下面代码在一个类中实例化了另一个类
#include<iostream>
using namespace std;
#include<string>
class Phone {
public:
Phone(string Name)
{
m_PName = Name;
cout << "phone的构造函数调用" << endl;
}
string m_PName;
~Phone() {
cout << "phone的析构函数调用" << endl;
}
};
class Person {
public:
Person(string Name, string PName) :m_Name(Name), m_Phone(PName)
{
cout << "person的构造函数的调用" << endl;
}
string m_Name;
Phone m_Phone;
~Person() {
cout << "person的析构函数调用" << endl;
}
};
void test01() {
Person p1("zhangsan", "huawei mate 30");
cout << p1.m_Name << "拿着" << p1.m_Phone.m_PName << "手机" << endl;
}
int main() {
test01();
system("pause");
return 0;
}
拷贝函数
拷贝函数使用时机:
- 使用一个已经创建完成的对象来初始化一个新的对象。
- 值传递的方式给函数传值。
//值传递
void doWork(Person p)
{
}
void testo2(){
Person p;//实例化,默认构造函数
//调用上面的函数并传入这个p
doWork(p);//拷贝构造函数
}
//值传递本身也就是拷贝一份新的数据到栈区
我们简单来分析一下,类的值传递和传入一个 int a 是一样的。关键是我们要理解“值传递本身也就是拷贝一份新的数据到栈区”这句话。(关于栈区与堆区详解可以自行csdn)。
值方式返回局部对象。
Person doWork()
{
person p1;//局部变量函数结束即释放
return p1;//返回的其实是拷贝
/*会调用拷贝构造函数*/
}
void test(){
Person p = doWork()//
}
大家可以在编译器中自行实现一下这串代码,相信可以加深大家对拷贝函数的理解。
深拷贝与浅拷贝
注意:!!!(难点与重点)
如果利用编辑器提供的函数进行拷贝会进行浅拷贝的操作。
浅拷贝:逐字节拷贝(即完全复制)。
若拷贝的对像在堆区开辟了一个指针"new int(shuju)",那么在析构函数中进行delete销毁操作(堆区开辟的数据由程序员自行销毁)也会进行两次,而第二次则是非法的,会导致程序崩。
可以用深拷贝的方式解决。重新申请内存空间
即不利用自行创建的函数,而是自行创建一个拷贝函数。
class Person
{
Person(const Person &p){
//m_Height = p.m_Height,默认拷贝函数自行创建的过程
m_Height = new int(*p.m_Height);
//重新开辟一块新的内存,可以解决上述非法操作。
}
~Person()
{
//有关指针代码的规范,防止野指针与空指针。
if(m_Height != NULL)
{
delete m_Height;
m_Height = NULL;
}
}
};
关于这几个函数我就介绍这么多吧。当然还有一些内容我没有讲到,也希望大家可以自己积极去了解。
this指针
具体在代码中解释吧。
#include<iostream>
using namespace std;
class Person {
public:
Person(int age)
{
//this指针可解决形参与成员变量同名的问题。
//this指针指向被调用的成员函数所属的对象。
//建议正确使用命名规范。
this->age = age;
}
Person& Person_age(Person p) {
this->age += p.age;
//返回对象本身。
return *this;
}
//需要用引用的方式返回,否则会拷贝一份新的数据,无法实现连环“.”(下面的链式编程思想)。
//引用则会一直赋值在该对象上。
int age;
};
void test01() {
Person p1(10);
Person p2(10);
//链式编程思想。
p2.Person_age(p1).Person_age(p1).Person_age(p1);
cout << "p1的年龄为" << p1.age << endl;
cout << "p2的年龄为" << p2.age << endl;
}
int main() {
test01();
system("pause");
return 0;
}
其实,简单点理解this指针就是“我自己”。有了这样的一种表述方法,我们就可以更方便地与编译器对话了。
封装 继承 多态
其实创建一个类本身就是一个封装的过程。所以这里我们就主要就继承与多态讲解。
继承:
“继承”正如字面意思,就是子类从父类那里继承父类的相关属性与方法(实际上继承了父类的全部内容,只不过存在权限允不允许访问的问题)
class IntelCpu :public:: CPU{}//即Intelcpu继承了父类cpu的内容。
继承时在 冒号后面会有“public 等,这涉及到继承权限的问题
1) public继承方式(不变)
- 基类中所有public成员在派生类中为public属性;
- 基类中所有protected成员在派生类中为protected属性;
- 基类中所有private成员在派生类中不可访问。
2) protected继承方式(public变为protected)
- 基类中的所有public成员在派生类中为protected属性;
- 基类中的所有protected成员在派生类中为protected属性;
- 基类中的所有private成员在派生类中仍然不可访问。
3) private继承方式(public变为private)
- 基类中的所有public成员在派生类中均为private属性;
- 基类中的所有protected成员在派生类中均为private属性;
- 基类中的所有private成员在派生类中均不可访问。
多态:
C++的多态性主要是重载和虚函数。(也就是静态多态性和动态多态性)
函数重载:即同一个名称但因为传入参数不同而可以分别实现不同功能。
#include<iostream>
using namespace std;
int zan(int x,int y) //进行加法运算
{
return x+y;
}
float zan(float x,float y) //进行乘法运算
{
return x*y;
}
int main()
{
int a=1;
int b=2;
float c=1;
float d=2;
cout<<zan(a,b)<<endl;
cout<<zan(c,d)<<endl;
return 0;
}
注意:::无法区分仅按照返回值区分的重载!!!**
运算符重载:两个类之间直接进行加号运算是无意义的。但我们可以(借助运算符重载)让其有意义。
用 operator"运算符" 来命名函数,可以是成员函数或全局函数。
// <>不需要写出
<返回类型说明符> operator <运算符符号>(<参数表>)
{
<函数体>
}
进行了运算符重载,你就可以通过运算符实现你所需要的功能,例如class person 有两个属性身高体重,你可以重载+运算符,从而实现通过类相加,让其两个属性都相加。但注意你的重载必须要方便理解,且有效。
注意:不会利用成员函数重载<<运算符,因为无法实现cout在左侧,只能用全局函数重载左移运算符。
因为使用成员函数只会这样:P1 << cout; 显然不符合代码的常理。
//ostream为cout对应的类型
ostream operator<<(ostream &cout,Person &p){
return cout;
}
//使其可以符合链式思想(即一直追加<< )。
虚函数:这里不再具体展开了,因为字数已经够多了。推荐查看c++虚函数详细讲解。简单一点理解,就是子类在继承父类时又想在父类基础上改进,于是发生重载,让自己改进的方法覆盖父类的方法。
对于c++类与对象希望大家可以系统深入地学习。因为上文对类与对象的讲解并不完整。友元,静态成员等等都没有讲解到,还有很多规范也没有涉及。但我希望这篇文章会对大家或多或少有所帮助,谢谢了。
声明:转载请注明出处。码龄不够,可能出现错误,欢迎批评指正。
尽管c++有很多人喷它,但却也有很多人爱它。无论如何,作为学习者而言我们都应积极应对它。因为努力与付出终会有回报。我们无数次尝试改变的可能,我们便拥有了无数次提升自我的机会,而未来也因为我们,有了更多发展的可能。加油,码农!
本文来自博客园,作者:litecdows,作者在其他博客平台均使用此昵称!
转载请注明原文链接:https://www.cnblogs.com/litecdows/p/15864398.html