目录
一、面向对象和面向过程的基本认识
我们都知道,C++是兼容C语言的,C语言是一门面向过程的编程语言,而C++一般用于面对对象编程。那么,面对对象和面对过程编程有何不同点呢?
1.面向过程编程简单来说就是通过分析解决问题的步骤,然后利用函数来完成解题步骤的详细设计,通过调用函数来解决问题。
2.面向对象编程简单来说就是通过分析问题面临的对象,将对象进行分组设计,将解决问题的办法分解成若干对象的问题解决。
3.上述区别可能会难以理解,其实我们可以将面向对象和面向过程编程联想成一个具体的实例,比如:洗衣服。
面向过程:洗衣服的步骤可以分为以下部分:
面向对象:洗衣服的相关对象有:盆子、人等。具体对象之间的联系和洗衣服的具体步骤不需要了解,会在对象的定义解决。
二、类的定义
C语言结构体中只能定义变量,在C++中,结构体内不仅可以定义变量,也可以定义函数。
C++中类的定义可以用struct或class关键字来定义。
区别:strcut定义的类中的成员变量默认是公有的(为了兼容C语言),而class定义的类中的成员
变量默认是私有的。
但是在C++中,更加倾向于用class来定义类(类的封装性):
class className
{
// 类体:由成员函数和成员变量组成
}; // 一定要注意后面的分号
类体由成员函数和成员变量组成,成员函数又叫类的方法,一般都是共有的(public);成员变量又叫类的属性,有三种分类:private、protected、public,分类的根据是成员变量访问的权限不同(后续的博客中会有介绍)。
注意:1.类定义(声明)时不开辟空间,只有实例化(定义类对象)时才会开辟空间。(可以类比成画 图纸和建房子,画图纸不会实际占用土地空间,只有按图纸实际盖房子时才会占用空间)
2.因为类的成员变量是属于每个类对象的私有部分,类的成员函数是属于所有对象的,是 一段公共区域的代码段,所以类的大小是成员变量的大小(要考虑内存对齐),其计算方法和 C语言中的计算结构体大小的方法相同。
3.空类(没有成员变量的类)的对象的大小是1个byte,不存储数据,起一个标志作用(占位 符),标记类对象成功创建。
三、类的特点
1.类的三大特点:封装、继承、多态
在类和对象阶段,主要是研究类的封装特性,那什么是封装呢?
封装:将数据和操作数据的方法进行有机结合,隐藏对象的属性和实现细节,仅对外公开接口来
和对象进行交互。
封装本质上是一种管理,让用户更方便使用类,并且不能随意访问或更改类的属性,对类的内部结构起到了一定的保护作用。在C++语言中实现封装,可以通过类将数据以及操作数据的方法进行有机结合,通过访问权限来隐藏对象内部实现细节,控制哪些方法可以在类外部直接被使用。
(继承和多态在后续的博客中会有说明)
2.类域
类定义了一个新的作用域,类的所有成员都在类的作用域中。在类体外定义成员时,需要使用 ::
作用域操作符指明成员属于哪个类域。
例如:
//类域
class Date
{
public:
Date(int year,int month,int day);
void Print();
private:
int _year;
int _month;
int _day;
};
Date::Date(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
void Date::Print()
{
cout << _year << '-' << _month << '-' << _day << endl;
}
int main()
{
Date d1(2023, 2, 12);
d1.Print();
return 0;
}
上述程序的运行结果:2023-2-12
程序中的::是作用域操作符,在类外定义成员函数和通过类名调用成员函数是都要用到。
类域是一块新定义的作用域,类内的成员变量和成员函数都属于这个类,他们的作用域就是这个类域。
四、成员函数
类的两种定义方式:
1. 声明和定义全部放在类体中,需注意:成员函数如果在类中定义,编译器可能会将其当成内联函数处理。
2. 类声明放在.h文件中,成员函数定义放在.cpp文件中,注意:成员函数名前需要加类名::,例如:Date::Date(){}
五、this指针
this指针就是类对象的地址。
1.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指针的根本目的是为了多个类对象调用成员函数时,成员函数可以区分应该操作的类对象。
2.this指针的特性
①成员函数的this指针不可修改,其数据类型为 类类型* const,例如:Date* const。
②this指针是编译器加给非静态成员函数的隐藏形参(只在成员函数内部使用),当对象调用成员函数时,将对象地址作为实参传递给this形参。所以对象中不存储this指针。
③this指针是“成员函数”第一个隐含的指针形参,一般情况由编译器通过ecx寄存器自动传递,不需要用户传递。
④this指针存储在栈上,成员函数调用结束后栈帧销毁,this指针还在,所以当函数返回值是类类型的时候,可以使用引用,减少拷贝构造的调用。(拷贝构造函数在后面的博客中会介绍)
相关的例题:
// 1.下面程序编译运行结果是? A、编译报错 B、运行崩溃 C、正常运行
class A
{
public:
void Print()
{
cout << "Print()" << endl;
}
private:
int _a;
};
int main()
{
A* p = nullptr;
p->Print();
return 0;
}
上述程序运行的结果是正常运行。
// 2.下面程序编译运行结果是? A、编译报错 B、运行崩溃 C、正常运行
class A
{
public:
void PrintA()
{
cout<<_a<<endl;
}
private:
int _a;
};
int main()
{
A* p = nullptr;
p->PrintA();
return 0;
}
上述程序的运行的结果是运行崩溃。
为什么?
将nullptr赋值给类对象指针后,并不影响*()和->的使用,nullptr是在调用类的成员函数时传给了成员函数的隐藏形参this指针,如果调用的成员函数的函数体没有对this指针进行*()和->的操作,则不会影响程序的正常运行。
这个问题的关键是理解nullptr的传递。两段代码中的p->PrintA()语句都没有对空指针进行->操作,而是通过地址为p的类对象调用了类域里面的成员函数Print(),并将nullptr赋值给this指针。两段程序运行结果不同是因为调用的成员函数的函数体,前者为对nullptr作任何操作,而后者访问了其成员变量,对nullptr进行了->操作,从而导致第二段程序执行时崩溃。
六、总结
本文主要介绍了对C++中类的初步认识以及相关的一些基本知识,剩下的内容会在接下来的博客中进行具体介绍。希望大家多多支持,也希望各位大佬能指点一二。