C嘎嘎入门篇:类和对象(1)
前言:
小编在之前讲述了C++的部分入门基础,读者朋友一定要掌握好那些,因为C++的学习和C有点不同,C++的知识都是比较连贯的,所以我们学好了前面才可以学习后面的内容,本篇文章小编将会讲述C++真正的入门篇:类和对象,学完这一部分后我们才可以说我们真的C++入门了,下面废话不多说,开启今天第一部分的讲解:
目录
1.类的定义
1.1.类定义的格式
1.1.1class关键字
1.1.2.类定义的两种方式
1.声明和定义放在一起
2.声明和定义分离
1.2.访问限定符
1.2.1.引例
1.2.2.访问限定符的概念
1.2.3.访问限定符的分类
1.2.4.访问限定符的使用
1.3.类域
1.3.1.类域的概念
1.3.2.类域的使用
2.实例化
2.1.实例化的概念
2.2.对象的大小
2.2.1.内存对齐的规则
2.2.2.实例化后对象的大小
3.this指针
4.两道小小的选择题
1.第一道选择题
2.第二道选择题
正文:
1.类的定义
1.1.类定义的格式
类是C++增加的内容,以小编的话来说,其实类就是我们在C学到的结构体的升级版,可能看到这里很多读者朋友会懵,不要急,等会小编就会慢慢的讲到,首先我们先说一下定义类类型的关键字。
1.1.1class关键字
class是C++用于定义类 类型 的关键字,这个记住就好,下面小编就先拿出一段代码来带大家去看一下如何用C++来书写类,也方便小编对于class的使用做出解释:
可以把class想成更加高级的struct,只不过它比结构体多了可以增加函数,并且类的名字就是类的类型
//class wang
//{
//public:
//成员函数
// void add(int a, int b)
// {
// cout << a + b << endl;
// }
// void Init(int a = 1,int b = 1) //后面学习的构造函数
// {
// _a = a;
// _b = b;
// }
//private:
// 成员变量
// int _a;
// int _b;
//};
//
首先不难看出,类和结构体的用法都是相似的,它们都是在对应的关键字后面加上名字,然后用{},最后别忘加个“;”就好,这个就是结构体的用法,小编在上面也说过,类就是结构体的升级版,之后比结构体更高级的是,类里面是可以放置函数的,我们把类里面的函数叫做成员函数(类的方法),与结构体一样,类里面也应该有成员,此时我们叫做成员变量(类的属性),这便是类的一些基本组成部分,当然,为了区分某些成员变量,就比如小编上面的成员函数Init的形参是a,b,我们在写成员变量的时候,可以在变量前面加上 _ 或者m开头,当然,C++对于成员变量的命名没有什么要求,这只是为了区分形参和成员变量而来的,可以看作一种惯例,写不写都可以的,全看自己。对于成员函数,小编在这里说一下,类里面的成员函数都默认为inline,也就是内联函数,对于内联函数小编在上一篇文章讲过,感兴趣的读者朋友可以看一下小编上一篇关于内联函数的介绍。
可能有很多读者朋友对于上面的public和private很好奇这几个单词出现在这里是为了什么,不要着急,小编稍后会讲到的。
1.1.2.类定义的两种方式
对于类,我们可以通过两种方式进行定义(主要是通过成员函数进行区分),下面我们先来讲述一下第一种:
1.声明和定义放在一起
class wang
{
int add(int a, int b)
{
return a + b;
}
int _a;
int _b;
};
此种情况其实就是小编在上面展示的代码,不难看出,此时我们在书写成员函数的时候,直接把它的定义给写出来了,此时我们函数的声明和定义是在一起的,这是我们书写类的第一种方式,不过不知道读者朋友是否还记着小编在之前数据结构的文章中,小编对于顺序表什么的书写都是函数的声明和定义分别在不同文件中,声明放在头文件,定义放在源文件中实现,所以我们在写类的时候,第二种方式就是声明和定义是分离的:
2.声明和定义分离
ceshi.h
class wang
{
int add(int a, int b); //此时就是函数的声明
int _a;
int _b;
};
ceshi.cpp
int wang::add(int a, int b) //对于前面的操作符小编也在文章后面说
{
return a + b;
}
此时上面就是类的定义和声明是分离的,此时我们只需要把我们想要写的类放在头文件中,然后把实现方法写在源文件就好了,这对于我们书写一些数据结构或者一些比较大的项目的时候是很实用的,各位读者朋友一定要掌握好这种写法。小编虽然目前还是个小白,但是我猜测后面我们实现一些数据结构的时候,对于类的使用应该就是这样的,不过现在肯定有很多小伙伴疑惑,小编在ceshi源文件写的 “wang :: ”是什么意思,为什么会用到域作用限定符了?难道类也是一种域?这是肯定的,小编在之前讲述域的时候,就说过C++是有四种域的,此时就是我们在之前没有讲过的:类域!对于类域的讲解,小编也是后面会讲,不要着急,慢慢来,下面小编先要讲述一下我之前没讲的,对于访问限定符的讲解!
1.2.访问限定符
1.2.1.引例
在讲述访问限定符之前,小编先通过一段代码来带大家去感受一下访问限定符到底是干啥的,此时我们就拿上面的代码举例:
class wang
{
int add(int a, int b)
{
return a + b;
}
int _a;
int _b;
};
int main()
{
wang s1; //我们可以认定此时wang是定义了一个s1的对象,这个叫做实例化,后面我会说
s1.add(1, 2); //这里为什么会报错?
return 0;
}
此时我们在进行编译的时候,发现编译器给我们报错,说我们无法访问private成员,这个其实就是其中一个访问限定符,它限制了我们去访问这个函数,所以此时我们便知道了访问限定符的作用,它是用来限定我们去使用类里面的数据的,下面我们正式来讲一下访问限定符的概念!
1.2.2.访问限定符的概念
C++是一种实现封装的方式,用类将对象(呜呜呜我也是有对象的人了),让对象更加的完善,通过访问的权限来选择性的将其接口提供给外部的成员使用,此时访问限定符就是可以决定我们对于类中元素的使用权限,这便是访问限定符的概念,从前面小编写过的代码我们可以看出访问限定符似乎有多种,下面小编就给各位读者朋友讲述一下访问限定符的分类:
1.2.3.访问限定符的分类
正如上图所示,在我目前我学习的C++的知识中,上面就是现阶段我们要学习的访问限定符,现在让小编带领各位去了解每个限定符的用法:
首先是 public 限定符,它的中文名是公开的,所以它再类的作用就是公有,以为着类外的元素可以访问被它修饰过的类里面的元素,之后是private和protected,经过翻译后的意思是私有和保护,在目前我们的学习中,我们可知道被这两个符号修饰过的类中的元素是不可以被外界元素访问的,这里可能很多读者朋友会有疑问了,那这两个作用不都是一样的吗?为啥还要区分开,确实,以我们目前的知识储备,他们都是一样的,但是其实我们在往后学,在继承章节我们才可以知道它们的不同,所以他俩还是不一样的,不然为什么祖师爷会分出这俩。这里小编爷爷奥提一嘴下面我们来讲一下如何使用访问限定符(其实小编在上面的代码已经展现了):
1.2.4.访问限定符的使用
这个也是蛮简单的,我们只要写访问限定符,再加上一个“ :”就好了,如下面代码所示:
class wang
{
public:
void Init(char * arr,int age,char* arr1)
{
strcpy(arr, _arr);
_age = age;
strcpy(_arr1,arr1);
}
private:
char* _arr;
int _age;
char* _arr1;
};
上面就是对于访问限定符的使用,对于访问限定符,我们也要知道它的作用域,它的作用域是从访问限定符开始,到下个访问限定符之前,如上面的代码,public的作用域就是在它:之后到private之前,而private的作用域是从它的:开始到},如果在类中只有一个访问限定符,那么它的作用域就是到 } 之前,也就是包含整个类域的。小编在之前也提到过类域,但是小编还没有讲,那么下面,我们开始进入类域的学习。
1.3.类域
1.3.1.类域的概念
类定义了一个新的作用域,类的所有成员都在类的作用域中。毕竟类就像我们再刚开始讲过的命名空间一样,我们都是把名字进行隔离了,我们在类外面同样也是可以用类里面的成员名来去使用这些名字的,目前为止,小编已经把四种域讲完了,不知道各位读者朋友是否还记得:分别是全局域,局部域,命名空间域,类域,下面小编将会讲述类域的一些使用注意事项
1.3.2.类域的使用
对于域的使用,小编在之前也说过,我们需要用到域作用限定符,也就是“:”,那么我们什么时候才会用到类域呢?我们一般在进行声明和定义分离到不同文件时候,我们才可以去使用类作用限定符,就拿小编上面讲的ceshi.cpp来说,通过这个我们就可以看见小编此时用到了域作用限定符,因为如果小编不加这个的话,编译器会默认到全局域中去查找这个函数,在全局域是不存在这个函数的,因为小编把函数声明放到类域里面的,除非我们使用了域作用限定符,否则编译器是不会去识别类里面的函数的,此时就展现了我们如何对类域进行使用,此时我们已经讲述完了类的一些基础知识,下面我们就要用类去创建对象了(对象来喽!),系好安全带,准备出发喽!
2.实例化
很多读者朋友一看到这个就感觉很懵,实例化是个啥玩意,小编当时也是觉得有些懵的,直到我学了以后,才知道,实例化通俗点来讲,就是用类去定义一个变量,就像我们定义整型变量,结构体类型的变量一样,但是也是有一些不同的,因为很多读者朋友在学完了类域的知识以后,认为我们在想要使用类里面元素的时候,可以直接用域作用限定符去使用,其实这是不可以的,因为我们类里面的成员变量只是声明,编译器并没有给他开辟空间,只有开辟了空间的我们才可以叫做定义,下面我们来好好说一下实例化的相关概念:
2.1.实例化的概念
用类类型在物理内存中创建对象的过程,称为类实例化对象,类是对象进行一种抽象概述,是一个模型一样的东西,限定了类有哪些成员变量,小编在上面说过,成员变量只是声明,并没有分配空间,用类实例化出对象以后,才会分配空间。
当然,一个类可以有多个对象,实例化出的对象是占物理空间的,其实此时,我们可以把类比做成一个图纸,而对象就是依靠图纸建造的房子,通过这个图纸可以建立好多个房子,就如下图所示:
上图就是类的一个类比图·,类就是建房子的图纸,而对象就是通过图纸建造的房子,所以只有建造房子的时候才会开辟空间,而类(图纸)并没有分配空间,这个是类比较重要的知识,各位读者朋友一定要好好的去理解
2.2.实例化的使用
其实对于类的实例化,和定义结构体变量是一样的,我们直接确定一个对象名直接建立就好,至于我们对于类里面成员的运用,和结构体一样 ,如果我们创立的不是指针,那么就用“.”操作符,如果是建立了类指针,那么就用“->”操作符,所以这里也能体现类就是结构体的升级版,不过和结构体不同的是,我们在这里进行定义的时候,类的名字就是类的类型,想当初我们学习结构体的时候,关键字+结构体名才代表着结构体的类型,不过在C++中,结构体名也是结构体的类型了,我们不在需要使用typedef关键字来进行改名了,这就是C++对于C的优化,所以从这里我们也看出来了C++的香了,不多废话,下面小编展示一下用完整的用类写完一个类(日期类):
using namespace std;
//日期类
class Date
{
public:
void Init(int year, int mouth, int day)
{
_year = year;
_mouth = mouth;
_day = day;
}
void Print()
{
cout << _year << "/" << _mouth << "/" << _day << endl;
}
private:
int _year;
int _mouth;
int _day;
};
int main()
{
Date s1;
s1.Init(2024, 8, 8);
s1.Print();
Date s2;
s2.Init(2024,8,7);
s2.Print();
return 0;
}
上面的代码就是小编用了完整的类写了个简易的程序,可以看出此时我们实例化后的使用,也可以看出类比C时的结构体的优点,因为类实现了方法和元素封装在一起的功能,这样会让我们去更方便的完成一些事情,上面的代码还体现了一个类可以实例化多个对象,这也印证了小编上面所说的,下面让我们看看上面代码的运行图:
可以看出成功运行出了程序,这便是实例化的使用,我们在之前无论学习什么类型,整形,指针类型,结构体类型……我们都知道了这些类型定义完后变量的大小,自然的,我们学习完了类的实例化以后,我们还需要知道实例化后对象的大小,这个也是很重要的,下面我们来进行对象大小的学习。
2.2.对象的大小
2.2.1.内存对齐的规则
我们想要去计算对象的大小,首先我们就要复习一下我们在C语言阶段学习的关于结构体内存对齐的知识,上面就是小编找之前学习内存对齐的时候记下的规则(当时我懒没有写博客),为了让读者朋友去更好的了解内存对齐的知识,小编通过一个例子来给各位读者朋友去更好的了解内存知识:
struct wang
{
char a;
int b;
int c;
};
对于这个题的大小小编通过图片和文字的方式进行讲解:
首先我们可以知道a是字符型,所以内存占据一个字节,而b和c都是整型,所以在内存中占据四个字节,所以我们可以知道对齐数是4个字节(4比8小,小编的编译器是VS,自然也是用的VS的规则),所以如图所示:
通过上图我们可以知道,此时我们的对齐数是9,但是此时的对齐数是4,不符合对齐数的倍数,所以大小应该是4的倍数也就是12个字节大小,下面来看一下此代码的运算图:
可以看出小编说的并没有错,那么知道了对齐知识以后,下面我们开始进入对象大小的探究喽!
2.2.2.实例化后对象的大小
对于实例化后对象的大小,得看类里面的元素,小编在上文说过,类是结构体的升级版,所以我们计算大小的时候,也是跟结构体一样,我们需要用到结构体内存对齐的知识,小编在之前偷懒没写过结构体的文章,前面小编说了如何进行结构体内存对齐的运算,当下我们的问题是,对于类里面的函数我们是否需要计算大小?那么,我们可以大胆的去猜测一下,类里面的函数是不需要去计算大小的,当然我说的肯定是不准的,下面小编来计算一下上面小编写过程序的代码:
using namespace std;
//日期类
class Date
{
public:
void Init(int year, int mouth, int day)
{
_year = year;
_mouth = mouth;
_day = day;
}
private:
int _year;
int _mouth;
int _day;
};
int main()
{
Date s1;
s1.Init(2024, 8, 8);
cout << sizeof(s1) << endl;
Date s2;
s2.Init(2024,8,7);
cout << sizeof(s2) << endl;
return 0;
}
通过我们前面讲过的内存对齐的知识,我们可以发现此时类里面的数据仅仅就是计算了成员变量的大小,并没有计算函数的大小,其实我们可以这么想,我们在进行类实例化对象以后,对象已经开辟了空间,所以此时的成员变量肯定就是已经开辟好了空间,那么成员函数呢?首先我们要知道,函数在被编译完以后是一段指令,在对象中是没有办法储存的,这些指令会单独放在一个区域,如果说对象中非要储存的话,只可以是成员函数的指针。这种说法可能会比较难理解,小编换一种说法,我们知道,当我们实例化对象以后,每个对象里面的成员变量肯定是各不相同的(如果相同的话那么类按道理说只可以实例化出一个对象了),但是我们类里面的方法(成员函数)肯定是一样的,它们都是实现的同一个功能,所以没必要再多去开坡空间,几个对象共用一个函数就好了,如果不一样的话,我们每创建一个对象,就要多为成员函数开辟一份空间,如果我们创建100个变量,意味着函数指针要重复100次,这样的话会显的很浪费,所以函数是不需要去开辟空间的,自然也就计算大小对象大小的时候只计算成员变量就好,小编现在准备拿出一个代码题来考验一下各位读者朋友,下面请看:
using namespace std;
class wang
{
public:
int add(int a, int b)
{
return a + b;
}
};
int main()
{
wang s1;
cout << sizeof(s1) << endl;
return 0;
}
可能对于这个代码,很多读者朋友会脱口而出,答案是0!如果按照小编之前的讲解,而且可以很清楚的看见小编写的这个类里面只有成员函数,这个答案是没错的,不过,如果这个题那么简单的话,小编也不会去让各位读者朋友去做这个题了,下面小编也不卖关子了,先给大家看一下运行图:
看到上面的结果,可能很多读者朋友会举着奇怪,难道成员函数是是开辟空间的?这里小编直接给大家解惑:成员函数确实是不开辟空间的,这里的一个字节,其实不是代表着成员函数的大小,它只是编译器开辟1个字节,来代表着:这个对象存在着,而不是啥也没有,仅仅是个标记的作用,各位读者朋友知道就好,可别下次遇到这种题了,你回答个:成员函数也开辟空间!那就闹笑话了。
根据小编之前的讲解,各位读者朋友知道了我们实例化出多个对象以后,它们的成员函数是共用的,但他们的成员变量都是互不相等,各自开辟空间的,那么小编问各位一个问题:那成员函数是如何区分每一个对象的呢?可能学过C++的读者朋友已经知道小编想要考察什么了,但我相信看这篇博客的大多都是没学过C++的,小编也不藏着掖着了,这些都和一个隐藏指针有关,它就是小编接下来要说的:this指针!
3.this指针
using namespace std;
//日期类
class Date
{
public:
void Init(int year, int mouth, int day)
{
_year = year;
_mouth = mouth;
_day = day;
}
void Print()
{
cout << _year << "/" << _mouth << "/" << _day << endl;
}
private:
int _year;
int _mouth;
int _day;
};
int main()
{
Date s1;
s1.Init(2024, 8, 8);
s1.Print();
Date s2;
s2.Init(2024,8,7);
s2.Print();
return 0;
}
为了小编的讲解,小编这就拿日期类进行举例,上面Date类中有Init和Print两个成员函数,函数体中是没有不同对象的区分的,为了解决函数解决不同对象的问题,C++特地引进了this指针来帮助函数去辨别不同对象的,就拿上面对于s1的Print函数为例,其实这个函数在编译器看来,是这么写的:
s1.Print(&s1);//这个是调用函数的时候,实参的内容
void Print(Date* const s1); //这个是形参的内容
上面其实就是成员函数的真实摸样,每一个成员函数都有一个隐藏的this指针, 编译器在编译后,成员函数都会在形参第一个位置,来去放置this指针,从而区分处理不同对象的情况,并且类的函数来访问成员变量时,实际上都是通过this指针进行访问的,可能此时很多读者朋友会想,我可不可以在写函数的时候就加上this指针呢?这是不可以的!C++规定不能在形参和实参的位置显示的写this指针(在编译的时候编译器会处理),不过吧,我们可以在函数内部写this指针,虽说可以写,但小编不推荐大家写出来,因为这样的话写的单词就多了(滑稽),不开玩笑啦,这个全凭自己的代码书写习惯,想写就写,不想写就不写,现在,我们已经学完了this指针和对象大小的知识,下面小编给两道选择题来考考大家知识掌握程度,一定要细心哦~
4.两道小小的选择题
1.第一道选择题
#include <iostream>
using namespace std;
class A {
public:
void Print() { cout << "A::Print()" << endl; }
private:
int _a;
};
int main() {
A* p = nullptr;
p->Print();
return 0;
}
我相信,很多读者朋友看到这个题,一看p是一个空指针,一看我们对p使用了解引用操作符,直接给出结果:无脑选A,小编当初也是认为这题选A,秒了!如果这个题真这么简单的话,那我就不会给出这个题了,以小编的老师来说,选A的,就该坐在电机椅上,让自己被电击清醒一下~这个题无论如何都是不选A的,这个就考察到了小编刚才讲的this指针以及成员函数,首先,Print函数里面会有个隐藏的this指针,这是读者朋友们清楚的;其次,小编说过,成员函数是类实例化对象以后所有对象共有的,所以成员函数是不开辟空间的,他在编译阶段就已经形成了一个函数指针,所以实际上这里我们看着是对函数进行了解引用操作,实际上这里我们仅仅就是调用了这个函数,并没有进行解引用,所以这个题应该是C.正常运行!各位读者朋友一定不要迷糊
2.第二道选择题
#include <iostream>
using namespace std;
class A {
public:
void Print() { cout << "A::Print()" << endl;
cout << _a << endl;
}
private:
int _a;
};
int main() {
A* p = nullptr;
p->Print();
return 0;
}
这个题和上个题神似,但是这个题目多了一行代码,就在Print函数内部,可能有些读者朋友又想选C,如果真想选C,用小编的话来讲,待遇和上面选A的一块处置,这里我们就用this指针来进行解答,我们知道,成员函数有个隐藏的this指针,它的作用就是来区分不同对象的,让我们进入函数内部,看看第二行代码,这真的仅仅是_a吗?那肯定是错误的,其实这里是this -> _a,此时我们成功的对空指针进行解引用操作了,这肯定编译就过不去,编译器没那么傻,所以这个题选A.编译报错!以上便就是小编根据学过的知识出的题目,各位读者朋友一定要好好的去理解这两个题目,这俩虽然看着简单,但是有很多坑,小编当时就跳进去了,所以各位读者朋友一定要好好的领悟小编前面讲过的知识~
5.C++中的结构体
本来小编写到上面就不打算写了,突然想到小编似乎只说过类是C结构体的升级版,但是没有说过C++中的结构体是怎样的,小编这就简单的说说,C++的结构体实际上和类的功能大部分是相通的,C++兼容了C中struct的语法,也把结构体升级成了类,结构体也是可以有成员函数,成员变量,结构体的名字就是结构体类型,我们再也不用像当初学习顺序表示用typedef对结构体改名了,这里可能有很多读者朋友会想,这结构体不就是类吗?其实他俩也是有一点差别的,我们如果在类中不用任何访问限定符,其实类默认是private(私有)的,而struct默认为public(公开)的,这是这俩不同的地方,它们可能也会有很多不同的地方,但是小编目前学的不算深,如果以后知道了小编·1会把这篇文章在进行更正修复的,虽说他俩是差不多的,但是小编还是推荐大家以后用class去定义类,毕竟这是C++了,我们一些语法也要跟着大众走了~
总结:
这篇文章的编写其实非常漫长,小编用了三天的时间才正式把这篇文章学完,不是这篇文章太难,只是小编这两天有点太放纵,太过于沉迷小说,动漫,各位读者朋友可不要学习小编,大家还是要去好好学习的,如果文章有错误,可以在评论区指出,我会及时的去更改,那么我们下篇文章见啦!