前言:
本篇主要的内容便是类和对象,它是C++的第一个有别于C语言很多的重要语法,同时我们也讲补充一些C++的语法细节。
1.类和对象:
1.面向过程和面向对象:
或许你听到很多人说过:C语言是面向过程的语言,而C++是面向结果的语言,但这里所说的面向过程和面向结果到底是指什么呢?
面向过程:面向的意思就是关注于哪些方面的意思,C语言关注的点就是过程,比如我们在书写C语言的代码时,我们的函数书写和指针判定是都不能缺少的,这就像是你只有一本说明书和一堆零件,现在你要自己动手把零件装上,你所关注的就是如何安装一台电视机的。
面向对象:C++所关注的点就是对象,我们C++代码的书写对对象有详细的描述,通过多个对象的关系的展开完成一段程序的执行,这就像你要装电视机,你有很多零件,你只要把零件和说明书交给会装零件的人,你就可以得到电视机,而你关注的点就是,零件,说明书,电视机,装机的人,他们都被称为对象,这就是面向对象的意思。
2.类:
1.类的形式:(class)
在C语言中,我们在一个结构体内部是不能定义函数的,而在C++里结构体内部是可以的,那为什么C++的结构体就可以了呢?
这时由于C++里的struct被自动升级为了类。
类的书写方式:
class className { // 类体:由成员函数和成员变量组成 }; // 一定要注意后面的分号
class为定义类的关键字,ClassName为类的名字,{}中为类的主体,注意类定义结束时后面分号不能省略。类体中内容称为类的成员:类中的变量称为类的属性或成员变量; 类中的函数称为类的方法或者成员函数。(注意,在类中成员变量和成员函数要分开看,他们的特点不同,这个之后会说)
类的主要作用就是创建一个作用域执行一个限定在类里面的程序,而同时又不会像C语言那样需要详细的过程,而是直接对着对象和对象之间的关系操作,这体现了类的面向对象的特性。
2.类的定义方式:
- 声明和定义全部放在类体中,需注意:成员函数如果在类中定义,编译器可能会将其当成内联函数处理。
2.类声明放在.h文件中,成员函数定义放在.cpp文件中,注意:成员函数名前需要加类名::
在这里,我更推荐第二种,因为在实际工程中我们往往把声明和定义分离,因此第二种写法更加常见和适用。
在我们以后的使用中:我们一般对于长的函数体选择分离写法,而对于短小的函数体则写在类的内部按照内联函数处理
补充:在C++中struct和class的的区别是什么,感觉两者没有区别呀:
首先C++兼容C的用法,这决定了在C++中C的结构体使用时没有问题的,但同时结构体也可以用来当作类,用法和class本身一样,区别在于struct默认public,而class默认private
3.类的访问限定符:
类的访问限定符分为三种:
1.public公开
2.protected保护
3.private私有
在当前阶段,我们姑且将其分为两种,public为一种,其余两种为第二种,其划分的依据是限定条件是否具有公开性
1.由public限定的类成员,出了类的作用域外是可以调用使用的,但由protected和private来限定的类成员,只能在类内进行访问和调用。
2. 访问权限作用域从该访问限定符出现的位置开始直到下一个访问限定符出现时为止
3. 如果后面没有访问限定符,作用域就到 } 即类结束。
4. class的默认访问权限为private,struct为public(因为struct要兼容C)!!!!
如下:
class hbw { private: SLDataType* _a; int _size; int _capacity; public: void SLInit(); void SLPrintf(); void Checkcapacity(); void SLPush(int x); };
在这个类里面,我们的类的成员变量都是私有的,而函数声明都是开放的,我们的访问限定符就是这样使用的。
但我不禁会问,设置这个有什么用呢?
我之前说过,C++是面向对象的语言,中间的过程一般是忽略的,故我们展现给操作者的应该是对象,对于操作者只要可以使用对应的功能即可,至于功能如何实现的这不是操作者需要解决的问题,而且对于不同操作者来说,由于思维模式的不同,很可能对功能的实现过程有别的思路,从而导致代码出现错误,故我们只开放功能而不展现过程就是为此。
4.类的命名:
让我们看下面的案例:
class Date { public: void Init(int year) { // 这里的year到底是成员变量,还是函数形参? year = year; } private: int year; };
我们的year=year中,到底是哪个year赋值给哪个year呢?这不仅你弄不清楚,计算机也很难弄清楚,故它采取了就近原则的方式,取最近的函数参数的year去进入函数进行操作。
但这样的代码会存在很大的误解,故我们要规范我们类的命名:我一般使用-year-双杠的形式来表示类里面的成员变量,以防止无法区分变量而出错,这是一个很好的习惯,也是C++不成文的规定,希望大家也遵从此方法或者自己有自己的表示方法。
改后的代码如下:
class Date { public: void Init(int year) { _year—— = year; } private: int _year_; };
这样就不会发生混淆问题,也让我们在阅读代码的时候能看的更清楚。
5.类的实例化:
首先清楚一个问题:声明和定义怎样区分呢?
我的理解是:在内存内部真正开辟空间的叫定义,反之叫声明。
故我们的类常常写在主函数进行前,显而易见此时我们还没开辟空间,我们的类其实就是声明而不是定义。
既然没开辟空间,我们就不能对类直接赋值,而是必须先创建一个内存中实际占用了内存的类的对象才可以,这便是类的实例化
例如:
int main() { // hbw._age = 19; // 编译失败:error C2059: 语法错误:“.” return 0; } • 1 • 2 • 3 • 4 • 5
这里就会报错,因为我们的hbw只是一个类,而我们还没对类进行实例化,也就是还没有实际占用内存的对象,那我们的赋值赋给一个根本不占内存空间的东西,这是不可能的,所以会报错,**这提示我们类必须先经过实例化才能访问。**但也引出了我对类的下一层理解,类到底是什么呢?
我又尝试了下一个方式:
class hbw { int a; }; int main() { hbw A; hbw B; hbw C; hbw D; return 0; }
我尝试创建4个hbw类的东西:A B C D,我发现代码并没有错误,这说明A B C D确确实实在内存中开辟出来并占用了内存,由此我又得到一个结论:一个类可以对应多个对象。
由这两个现象,我已经充分知道,类相当于蓝图,比如我们要买汽车,销售员给我们看汽车的详细蓝图,蓝图再详细再精美,终究不是我们要开的车,而车经过蓝图就可以被造出来被我们购买,我们通过类所创建的对象就相当于通过蓝图而制造的汽车,蓝图本身是不占空间的,而蓝图创建的对象才会在内存中实际开辟出空间来,我们要实际操作和修改的也是对象而不是类本身。
如下两张图:
但我们真正要的实体是:
6.类的大小模型计算:
首先说我们之前提到过的一句话:
类的成员包括两种,成员变量和成员函数,在编译器进行编译链接时,成员变量被实际开辟在一定的空间内部,而成员函数由于声明或者内联,一般是直接找到相应的函数定义的位置展开或者直接内联展开**,这说明类的大小是不考虑成员函数的,只考虑成员变量的内存大小之和,其计算方法同样符合C语言的结构体内存对齐规则。**
由此,我对类里的存储方式是这样认为的:
它大致分为两个方面,第一个部分存储成员变量,作为这个类的独有的属性被实际存储在类中,而对于成员函数则被存储在第二个部分,由于很多成员函数的定义相同,这部分可以说是共用的,他们不是这个类的独有属性,故成员函数被存储在公共内存区域内,不占用类开辟的对象的内存空间
如下图:
7.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, d2; d1.Init(2022,1,11); d2.Init(2022, 1, 12); d1.Print(); d2.Print(); return 0; }
让我们取出来这段:
d1.Init(2022,1,11);
d2.Init(2022, 1, 12);
我们的传参仅仅只有数字而没有相应的标识,那编译器是如何分辨哪一个是d1的传参哪一个是d2的传参呢?
这里便用到了this指针
1.this指针的特性:
- this指针的类型:* const,即成员函数中,不能给this指针赋值。
- 只能在“成员函数”的内部使用,且要注意,this指针是在非静态成员函数内部,而不是在对象中,这个别搞错了
- this指针本质上是“成员函数”的形参,当对象调用成员函数时,将对象地址作为实参传递给
this形参。所以对象中不存储this指针。 - this指针是“成员函数”第一个隐含的指针形参,一般情况由编译器通过ecx寄存器自动传
递,不需要用户传递,也就是说我们不需要在函数里面写出来this指针,是编译器自动加上的一个参数,我们就默认知道有一个就行
注意:this指针是可以为空的,当时一般不是赋值为空,而是强制转换为空,但这个基本不常用
8.小结:
学完了C++的类,我们回想一下我们如何用C和C++分别写一个顺序表的增删查改呢?
倘若用C写,我们的每一步都要传指针,还要对指针判断空,同时还不能忘了加箭头,但是使用C++后,我们的函数和定义不分离,对于打印和初始化函数我们甚至不需要传参这便是C++类的优势所在,也是C++在C的基础上的优化之处。
2.其他C++语法小知识点汇总:
1.auto关键字:
auto关键字可以自动推导右边变量的类型并且将这个类型赋给左边我们需要的变量。
例如:
int a=0; int b=a; auto c=a; auto&d=&a; auto s=&a; auto*m=&a;
在这里,我们的c的类型就是int,d的类型就是int*,s也为int*,m为int&类型,故我们由此得知,指针的auto写或不写星号都是可以的,但强调引用时必须要写&号的,否则与int没法区分了
补充一个打印变量类型的函数:typeid().name()
auto的注意点:
1.由于有自动识别功能,故auto必须初始化一个明确的变量,这一点和引用类似
2.auto不能当作函数的参数类型和返回类型,不支持。
3.auto不能用来声明和定义数组。
而auto的真正价值在于:
它可以把一个长的变量类型利用auto缩短,例如:
vextor<string>::iterator it=v.begin(); auto it=v.begin();
这样,化繁为简,这就是auto的最佳用法
2.范围for(C++代码糖)
在C++里,我们遍历数组有这样一种方式:
for(auto e:array) { cout<<e<<" "; } cout<<endl; return 0;
这便是用范围for来遍历数组,它的基本工作原理是:将数组中的每一个元素取出来先赋值给e,然后依次打印出e,但这里是可以自动判断结束和自动++向后走的,体现了C++的面向对象的思想,array我们今后会逐渐掌握,我们在这里主要是了解这种用法。
范围for的注意事项:
1.范围for是不能在函数内部用的,因为我们的函数传数组传的不是整个数组,而是数组的第一个元素的地址,故函数内部无法利用范围for遍历。
2.倘若想要改变数组里的元素,要把e改为auto&引用类型后,再对e进行操作,改成*类型是没用的,因为这里是赋值给e,赋值本身为int类型,int类型是没法转为int类型的,这里面是引用的独特用法,指针是没法做到的。**
3.指针空值nullptr
在C++中由于把NULL定义了一个宏,#define NULL 0;
即NULL变为了0,而不是原本的空的意思了,不过由于本身NULL为0在计算机中也是判定为假的,故并无太大影响,但这依旧是一个bug.
故我们引入一个nullptr来作为C++的空指针,它就相当于C语言中的NULL,以后初始化的时候就用nullptr而不用NULL,有时候可能会报错。
3.总结:
本篇我们详细介绍了C++的第一个相当好用的类和对象的知识点,它在使用层面上就让我们认识到它不同于C语言的极大的优势性,同时我们又学习了三个C++的小知识点,但C++还有很多更为复杂,更为庞大的知识,我想我们应该更进一步,不断向前,争取掌握计算机历史上最优秀的语言之一C++!