一、面向过程和面向对象的区别
C语言是面向过程的,关注的是过程,分析解决某件事的方法和步骤,通过函数来实现逐步解决问题。比如说点外卖可以分为拿起手机,打开美团,搜索食品,点击下单,付款,商家拿食材,做外卖,外卖小哥送外卖的过程,面向过程关注的是这里每一个步骤的解决方法,逐一解决问题。
C++是面向对象的,关注的是对象,把一件事分解成多个不同的对象,靠对象之间的交互完成。同样是点外卖这件事,可以把这件事分成3个对象,我,商家和外卖小哥,这个点外卖的过程是我点外卖,商家做外卖,外卖小哥送外卖,整个过程就是我,商家,骑手三个对象之间交互完成的,我根本不关心商家怎么做这个外卖的,骑手又是怎么把这个外卖送过来的,这个就是面向对象,面向对象不关心解决问题的过程,只关心各个对象之间的分工是啥就i行了。
二、什么是类?
C语言结构体中只能定义变量,在C++中,结构体内不仅可以定义变量,也可以定义函数。在C++中结构体升级成了类,在类中定义的变量成为成员变量,在类中定义的函数成为成员函数。
2.1 类的定义
class className//(class是类的关键字,className是类名(自己定义)) { //类体:有成员函数和成员变量组成 //例如: //成员函数 int Add(int x,int y) { return x+y; } //成员变量 int a; int b; };
class为定义类的关键字,ClassName为类的名字,{}中为类的主体,注意类定义结束时后面分号不能省略,和结构体类似。
类体中内容称为类的成员:类中的变量称为类的属性或成员变量; 类中的函数称为类的方法或者成员函数。
类的定义方式1:
声明和定义全部放在类体中,注意:成员函数如果在类中定义,编译器可能会将其当成内联函数处理。
class A { public: //声明和定义全部放到类里面 int Add(int x, int y) { return x + y; } private: int a; int b; };
类的定义方式2:
类声明放在.h文件中,成员函数定义放在.cpp文件中,注意:成员函数名前需要加 类名::
//类的声明放在头文件 A.h 中 class A { public: int Add(int x, int y); private: int a; int b; }; //成员函数的定义放在 A.cpp 中 #include "A.h" int A::Add(int x, int y) { return x + y; }
一般情况下,在做项目的时候都是用第二种方式。
成员变量命名规则:
一般情况下为了使成员变量和其它变量能够更好地区分开,我们都在成员变量名前面或者后面加上一条_。
例如:
class A { public: void Init(int a, int b) { _a = a; _b = b; } private: //成员变量 int _a; int _b; };
或者:
class A { public: void Init(int a, int b) { a_ = a; b_ = b; } private: //成员变量 int a_; int b_; };
其它形式的命名也是可以的,只要能加以区分就行了。
三、类的访问限定符和封装
3.1 访问限定符
C++实现封装的方式:用类将对象的属性(成员变量)与方法(成员函数)结合在一块,让对象更加完善,通过访问权限选择性的将其接口提供给外部的用户使用。从而更好地实现了封装。
【访问限定符说明】
- public修饰的成员在类外面可以直接被访问。
- protected和private修饰的成员在类外面不能直接被访问(此处protected和private是类似的)
- 访问权限作用域从该访问限定符出现的位置开始直到下一个访问限定符出现时为止。
- 如果后面没有访问限定符,作用域就到 } ,即类结束。
- class的默认访问权限为private,struct为public(因为struct要兼容C语言)。
注意:访问限定符只在编译时有用,当数据映射到内存后,没有任何访问限定符上的区别。
C++中struct和class的区别是什么?
C++需要兼容C语言,所以C++中struct可以当成结构体使用。另外C++中struct还可以用来定义类。和class定义类是一样的,区别是struct定义的类默认访问权限是public,class定义的类默认访问权限是private。
3.2 封装
什么是封装?
在C++中封装就是将数据和操作数据的方法进行有机结合,通过访问限定符隐藏对象的属性和实现细节,仅对外公开接口来和对象进行交互。
封装本质上是一种管理,让用户更方便使用类,只需要使用类提供的方法接口就行,不需要关心这个方法在底层使怎么实现的。
四、类的作用域
类定义了一个新的作用域,类的所有成员都在类的作用域中。在类体外定义成员变量或者成员函数时,需要使用 :: 作用域操作符指明成员属于哪个类域。
例如:
class A { public: void Print(int a, int b); private: int _a; int _b; }; //需要指明是A::的Print函数 void A::Print(int a, int b) { cout << a << ":" << b << endl; }
五、类的实例化
一个类就相当于房子的设计图,类里面的成员变量只是变量的声明,变量的声明是不开辟空间的,就好比一张设计图是不占用空间的,只有按照这张图纸真正地建起一栋房子的时候才会开辟空间,占用空间,也就是说只有这个类实例化出来一个对象的时候这些成员变量才会开辟空间。
用类类型创建对象的过程就成为类的实例化。
- 类是对对象进行描述的,是一个模型,限定了类有哪些成员,定义出一个类并没有分配实际的内存空间来存储它,比如设计图就是一个类,来描述这间房子的具体信息(有几个客厅,房间,在什么位置等等)。
- 一个类可以实例化出多个对象,实例化出的对象 占用实际的物理空间,存储类成员变量。
class A { public: void Print(int a, int b) { cout << a << ":" << b << endl; } int _a; int _b; }; int main() { //这样写是错误的,因为A这个类本身没有空间,相当于一张设计图 //需要实例化出对象的时候才会开辟空间 A._a = 10; A._b = 20; //这样写才是正确的 A a; a._a = 10; a._b = 20; return 0; }
六、类对象的大小
class A { public: void Print(int a, int b) { cout << a << ":" << b << endl; } private: int _a; int _b; }; int main() { //计算类的大小 //方法一: cout << sizeof(A) << endl; //方法二: A a; cout << sizeof(a) << endl; return 0; }
类中既可以有成员变量,又可以有成员函数,类的对象中包含了什么?计算类的大小的时候需要计算哪些成员?
如果对象中包含类的各个成员(成员变量和成员函数),我们知道,每个对象中的成员变量是该对象私有的,但是当不同的对象调用成员函数的时候,貌似只有传的参数不同,而函数的代码都是相同的,如果每一个对象都需要保存一份代码的话,很显然,代码是冗余的,实际上只需要一份就够了,所以为了节省空间,成员函数的代码只保存一份,并且不是保存在对象里面,而是保存到一个公共代码区,由整个类共享,所以成员函数不属于某个具体对象,计算对象的大小的时候只需计算成员变量的大小就是该对象的大小(需要考虑内存对齐)。即对象里只包含成员变量,不包含成员函数。验证如下:
class A { public: void Print(int a, int b) { cout << a << ":" << b << endl; } private: int _a; int _b; }; class B { public: void Print(int a, int b) { cout << a << ":" << b << endl; } }; class C {}; int main() { cout << "A1:" << sizeof(A) << endl; cout << "A2:" << sizeof(B) << endl; cout << "A3:" << sizeof(C) << endl; return 0; }
运行结果:
可以看到,A有两个int的成员变量,大小是8字节;B没有成员变量,只有成员函数,占用了1个字节;C是空类,既没有成员变量,也没有成员函数,占用了1个字节。
注意空类的大小,空类比较特殊,编译器给了空类1个字节来唯一标识这个类的对象。B和C都可以认为是空类,因为它们都没有成员变量,即对象中没有属于自己成员,所以B和C的大小都为1。
七、this指针
7.1 什么是this指针?
我们先写一个日期类:
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; Date d2; d1.Init(2023, 4, 29); d1.Print(); d2.Init(2022, 3, 26); d2.Print(); return 0; }
可以看到,Date类中有 Init 与 Print 两个成员函数,函数体中并没有关于不同对象的区分,那当d1调用 Init 函数时,该函数是如何知道应该设置d1对象,而不是设置d2对象呢?
C++中通过引入this指针解决该问题,即:C++编译器给每个“非静态的成员函数“增加了一个隐藏的指针参数,让该指针指向当前对象(函数运行时调用该函数的对象),在函数体中所有“成员变量”的操作,都是通过该指针去访问。只不过所有的操作对用户来说是透明的,即不需要用户自己来传递,由编译器自动完成。
7.2 this指针的特性
this指针的类型:类类型* const,例如日期类的this指针为:Date* this const,即成员函数中,不能给this指针赋值。
只能在“成员函数”的内部使用。
this指针本质上是“成员函数”的形参,当对象调用成员函数时,将对象地址作为实参传递给this形参。所以对象中不存储this指针。
this指针是“成员函数”第一个隐含的指针形参,由编译器通过ecx寄存器自动传递,不能显式地传递this指针。
重点:
1、this指针存放在哪里?
this指针是成员函数的形参,属于局部变量,所以this指针是存放在栈区的。
2、this指针可以为空吗?
this指针可以为空,但是当this指针为空的时候调用成员函数就不能访问成员变量,因为访问成员变量需要this->成员变量,如果this指针为空就会导致程序崩溃。
// 1.下面程序编译运行结果是? A、编译报错 B、运行崩溃 C、正常运行 //答案是C,正常运行,因为虽然this指针是nullptr,但是调用Print的过程中 //并没有用this访问成员变量,所以程序是正常运行的 class A { public: void Print() { cout << "Print()" << endl; } private: int _a; }; int main() { A* p = nullptr; p->Print(); return 0; }
// 2.下面程序编译运行结果是? A、编译报错 B、运行崩溃 C、正常运行 // 答案是B,程序崩溃,因为this是空指针nullptr,而在调用成员函数的时候 // 利用了this指针访问成员变量,对空指针解引用会导致程序崩溃 class A { public: void Print() { cout << _a << endl; } private: int _a; }; int main() { A* p = nullptr; p->Print(); return 0; }