1.面向过程与面向对象的初步认识
面向对象和面向过程是两种编程思想。面向过程是一种以事件为中心的编程思想,编程的时候把解决问题的步骤分析出来,然后用函数把这些步骤实现,在一步一步的具体步骤中再按顺序调用函数。
而面向对象是一种以“对象”为中心的编程思想,把要解决的问题分解成各个对象,建立对象的目的不是为了完成一个步骤,而是为了描叙某个对象在整个解决问题的步骤中的属性和行为。
面向过程和面向对象各有优缺点。面向过程强调代码的短小精悍,善于结合数据结构来开发高效率的程序。但需要深入思考,耗费精力,代码重用性低,扩展能力差,后期维护难度比较大。而面向对象程序设计中代码间的相关性低(低耦合特性),使得代码很容易被复用和扩展,同时也说明了面向过程的代码重用性低、扩展能力差。但开销大,当要修改对象内部时,对象的属性不允许外部直接存取,所以要增加许多没有其他意义、只负责读或写的行为。这会为编程工作增加负担,增加运行开销,并且使程序显得臃肿。
2.类的引入
C语言结构体中只能定义变量,在 C++ 中,结构体内不仅可以定义变量,也可以定义函数。比如:
之前在数据结构初阶中,用C 语言方式实现的栈,结构体中只能定义变量 ;现在以 C++方式实现,
会发现struct中也可以定义函数,对于这样的结构体,c++中升级成了类。
因为c++兼容c,一个结构体我们利用struct创建变量,也可以直接结构体名加变量来创建。
typedef int datatype; //利用结构体定义栈,内部可定义成员函数 struct Stack { //初始化 void stinit(int _capacity) { arr = (datatype*)malloc(sizeof(datatype) * _capacity); if (arr == nullptr) { return; } size= 0; capacity = _capacity; } //入栈 void stackpush(const datatype& _data) { arr[size] = _data; size++; } void stackpop() { size--; } datatype gettop() { return arr[size-1]; } void destroyst() { if (arr!= nullptr) { free(arr); arr = nullptr; size = capacity = 0; } } int* arr; int size; int capacity; }; int main() { struct Stack ST; ST.stinit(5); ST.stackpush(1); ST.stackpush(2); ST.stackpush(3); ST.stackpush(4); cout << ST.gettop() << endl; ST.stackpop(); cout << ST.gettop() << endl; return 0; }
如上我们定义了成员函数和成员变量,可以值间接调用成员函数。在这里struct 已经作为一个类在用了。
3.类的定义
在上述我们知道了struct 也可作为类,那么类的定义是什么?什么样的结构叫做类?
其实类是一种用户自定义的数据类型,它描述了一组有相同特性(属性)和相同行为(方法)的一组对象的集合。一个类的定义通常包含两部分的内容,一是该类的属性,另一部分是它所拥有的方法。这样的结构类型就可叫做类。
而对于c++,除了常用的结构体是类,c++也提供了一个关键字class专门用来定义类。这也是大多数人选择的方法。
class className { // 类体:由成员函数和成员变量组成 }; // 一定要注意后面的分号
class 为 定义类的 关键字, ClassName 为类的名字, {} 中为类的主体,注意 类定义结束时后面 分
号不能省略 。
类体中内容称为 类的成员: 类中的 变量 称为 类的属性 或 成员变量 ; 类中的 函数 称为 类的方法 或者
成员函数。
类的两种定义方式:
1. 声明和定义全部放在类体中,需注意:成员函数如果在类中定义,编译器可能会将其当成内
联函数处理。
2. 类声明放在.h文件中,成员函数定义放在.cpp文件中,注意:成员函数名前需要加类名::。
这与我们之前实现数据结构类似,函数的实现我们一般都放在.c文件当中,c++也如此。
4.类的访问限定符及封装
同样是类,为什么在选择类的定义是,人们一般不用struct,而取用clss,这里就要说到一点他们的访问限定符的区别。
在C++中,可以使用以下访问修饰符在进行声明时指定类型或成员的可访问性:public、protected、private。
.public: 可以被该类中的函数、子类的函数、其友元函数访问,也可以由该类的对象访问。
.protected: 可以被该类中的函数、子类的函数、以及其友元函数访问,但不能被该类的对象访问。
.private: 只能由该类中的函数、其友元函数访问,不能被任何其他访问,该类的对象也不能访问 。
而对于我们刚才说到的struct,它的默认访问限制是public,外部都是可以访问的。而对于class,它的默认访问限制是private,一般除了类中的是无法访问的。而对于真正实现大项目时,为了防止相互可能存在访问的情况下,private对于程序员来说更好。这就是为什么大多数人选择class,其次在继承和模板参数列表位置,struct和class也有区别。
注意:访问限定符只在编译时有用,当数据映射到内存后,没有任何访问限定符上的区别
封装:
首先,面向对象的三大特性分别是:封装、继承、多态。
在类和对象阶段,主要是研究类的封装特性,那什么是封装呢:
封装是指一种将抽象性函数接口的实现细节部分包装、隐藏起来的方法。封装可以被认为是一个保护屏障,防止该类的代码和数据被外部类定义的代码随机访问。要访问该类的代码和数据,必须通过严格的接口控制。封装最主要的功能在于我们能修改自己的实现代码,而不用修改那些调用我们代码的程序片段。适当的封装可以让程式码更容易理解与维护,也加强了程式码的安全性。
在C++语言中实现封装,可以通过类将数据以及操作数据的方法进行有机结合,通过访问权限来
隐藏对象内部实现细节,控制哪些方法可以在类外部直接被使用。
对于类的内联函数,还是一样,函数不会进入符号表,直接展开。
5.类的作用域
对于类和一般定义的空间的内部的访问,他们一般都是有空间访问限制的,这就需要在访问时加上结构::成员,像这种结构就跟之前学的namespace的定义一样,在实现struct class这种结构时,其实也定义了空间。是不被外部访问的,需要借助域访问作用符。
6.类的实例化
类是对对象进行描述的,是一个模型一样的东西,限定了类有哪些成员,定义出一个类并没
有分配实际的内存空间来存储它;
定义的类就是一种类型,我们可以通过这种类实例化多个对象。
class Person { public: //方法一 //方法二 //方法三 private: //他的属性 char* name; int age; int scoer; int id; }; int main() { Person p1; Person p2; Person p3; }
类实例化出对象就像现实中使用建筑设计图建造出房子,类就像是设计图 ,只设计出需要什么东西,但是并没有实体的建筑存在,同样类也只是一个设计,实例化出的对象 才能实际存储数据,占用物理空间。
对于一个类,它的变量只是声明,所有的成员函数不在类中存储且只是声明,它们会被提供一块公共的代码区域,一面每个对象都能访问且不同。
7.类的对象的大小的计算
一个类的大小,实际就是该类中”成员变量”之和,当然要注意内存对齐,注意空类的大小,空类比较特殊,编译器给了空类一个字节来唯一标识这个类的对象。
类的大小计算遵循结构体的对齐原则, 与普通数据成员有关,与成员函数和静态成员无关。虚函数和虚继承对类的大小有影响,是因为虚函数表指针和虚基表指针带来的影响。空类的大小为1。类只是一个类型定义,它是没有大小可言的。用sizeof运算符对一个类型名操作,得到的是具有该类型实体的大小。当类不包含虚函数和非静态数据成员时,其对象大小也为1。虚函数本身和其他成员函数一样,是不占用对象的空间的
class A1 { public: void f1() {} private: int _a; char c; }; //只有成员函数 class A2 { public: void f2() {} void f3() {} }; // 类中什么都没有---空类 class A3 {}; int main() { cout << sizeof(A1) << endl; cout << sizeof(A2) << endl; cout << sizeof(A3) << endl; return 0; }
根据编译的结果可以看到成员函数实际上是不计入大小的,只记入成员变量。空类认为是1。
结构体内存对齐规则
1. 第一个成员在与结构体偏移量为0的地址处。
2. 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。
注意:对齐数 = 编译器默认的一个对齐数 与 该成员大小的较小值。
VS中默认的对齐数为8
3. 结构体总大小为:最大对齐数(所有变量类型最大者与默认对齐参数取最小)的整数倍。
4. 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整
体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍 。
根据类的大小我们可以看出成员函数的栈帧是不在类里定义的。
内存对齐的设计是为了便于访问。
8.类成员函数的this指针
1.this指针的特性:类型:类类型*const,在成员函数中无法赋值,无法作为实体参数。
2.只能在成员函数中使用
实际上是通过传入对象的指针来确定对函数的调用,类中定义的仅仅是个声明,不可能定义多个对象都去访问类中的变量。
其次我们虽然知道是有个this指针,但不可以直接传个对象指针作为this指针,this指针可以在函数内部访问成员变量会其他成员函数,但不能实体化作为参数,他是隐形的。
注意事项:
上述三种给三种情况 ,A .编译错误 B.运行崩溃 C.正常运行
对于第一个,可以看到定义了一个对象指针 且为空,之后利用对象指针访问函数。首先我们会想到空指针访问需要解引用,但不会报编译错误,排除A,但实际上选C,正常运行:因为成员函数不存放在类中且也不存放在对象中,(存在对象中会是对象占良大量空间),成员函数实际是存在一块公共代码区域的,调用函数直接拿函数名去找这个函数的地址,因此并会对对象指针解引用,直接去访问函数。
第二个,与第一个原理类似,这里也不会报错,即使我们这样去写,但编译器不会解引用指针,也是直接那函数名去公共代码空间哪里找。
第三个,会运行崩溃,可以发现打印中会去访问对象的成员变量,空指针会去解引用,因此会运行崩溃。
其次在vs传递this指针时是通过寄存器来传递的。