一.面向过程和面向对象初步认识
c语言是面向过程的语言,关注的是步骤。
c++是面向对象的语言,关注的是事件完成需要的对象。
面向对象三大特性:封装、继承、多态
就洗衣事件,c语言和c++处理方式不同。
二.类的引入
c中可以自定义结构体,将一些相关联的数据组装成一种自定义数据类型。比如,我们在前面写的栈、堆。在这里我拿人这个结构体来举例。
c++在此基础得到启发,其创始人设计将具有一些特殊关系的变量和函数定义在同一类中。同样,以人这一类来举例。
当然,因为c++是在c语言基础上繁衍生长的,c++既完善优化c语法的一些漏洞,同时也继承c语言的一些优秀的特性。
在c++中,定义类可以使用关键字class和struct。
访问结构体内的成员变量:
结构体类型.成员变量
结构体类型指针->成员变量
三.类的定义
类是某方面有相同属性的事物元素的集合。
对象是某类一个具体的元素/事例。
class ClassName
{
类体(成员变量、成员函数)
}
class为定义类的关键字,ClassName为类的名字,{}中为类的主体,注意类定义结束时后面分
号不能省略。
类体中内容称为类的成员:类中的变量称为类的属性或成员变量; 类中的函数称为类的方法或者
成员函数。
类定义方式一 :声明和定义放在类中
成员函数如果在类中定义,编译器可能会将其当成**内
联函数(也就是在文件预处理阶段展开,不开辟空间创建函数栈帧)**处理。
类定义方式二:类声明放在.h文件中,成员函数定义放在.cpp文件中
.h文件
.cpp文件
注意:成员函数名前需要加类名::
注意:c++成员变量前要加下滑符(age) ,因为函数参数一般是age,成员变量也是age,会造成歧义。所以规定c++成员变量前要加下滑符号。
四.类的访问限定符及封装
封装:将数据和操作数据的方法进行有机结合,隐藏对象的属性和实现细节,仅对外公开接口来和对象进行交互。
封装本质上是一种管理,让用户更方便使用类。
好比计算机是将硬件封装起来,用户可以通过键盘、鼠标地对相应的硬件进行操作。如果不封装起来,用户就要自己去按动硬件上的开关对硬件进行操作。
类的访问限定符
- 访问权限作用域从该访问限定符出现的位置开始直到下一个访问限定符出现时为止
- 如果后面没有访问限定符,作用域就到 } 即类结束。
- class的默认访问权限为private,struct为public(因为struct要兼容C)
问:C++中struct和class中的区别?
答:C++中struct可以定义结构体,也可以定义类,其限定权限访问符为public。而class定义类,其限定权限访问符为private。
五.类的作用域
类定义了一个新的作用域,类的所有成员都在类的作用域中。在类体外定义成员时,需要使用 ::
作用域操作符指明成员属于哪个类域。
与命名空间定义的域可以对照来学,比如命名空间中的变量是全局变量,函数的限定权限访问符也是public。而类中定义的变量是类成员变量,其限定权限访问符往往是private,其内定义的函数的限定权限访问符视情况而定。
六.类的实例化
以类为特定数据类型,像定义其他普通数据类型变量相同。
以上的People类为例,创建变量p1:
People p1
类的实例化可以理解为建房子,类的定义和声明并不占据空间,相当于房子的设计图纸。只有类实例化对象,对象是实实在在地占据空间大小,相当于建造一栋真实存在的房屋。
七.类的对象大小的计算
如何计算类对象的大小?
思考:类中存在类成员变量、类成员函数。可能会存在的计算类对象的大小有几种呢??
1.成员变量和成员函数都计算在内
2.将成员函数存放在同一区域,传该区域的指针,计算指针和成员变量的大小
3.只计算成员变量的大小
而事实证明是,只计算类成员变量的大小。这是为什么呢?在C++中默认类中所有的成员函数存放在常量区(代码块)处,而不是存在类变量里。所以类的多个对象的相同类成员函数地址相同。没有必要在计算类对象大小的时候也将类函数地址计入在内,所以第一种方法不适用。
第二种方法,将类函数存放同一区域,将其区域地址计算在内计算。因为所有的成员函数存放在常量区(代码块)处,类多个对象的存放类成员函数的区域地址是相同的,而且是计算机可以随意访问的,所以也没必要在计算类对象大小的时候也将存放类函数区域地址计入在内。
第三种是最适合的方法。
在这里,计算类成员变量的大小要遵循计算结构体成员变量的大小的规则。
结构体内存对齐规则
- 第一个成员在与结构体偏移量为0的地址处。
- 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。
注意:对齐数 = 编译器默认的一个对齐数 与 该成员大小的较小值。
VS中默认的对齐数为8 - 结构体总大小为:最大对齐数(所有变量类型最大者与默认对齐参数取最小)的整数倍。
- 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整
体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。
八.类成员函数的this指针!
终于到本篇博客,我认为最重要的一个环节。之前也是跟着学校学习过一些基础java知识点,当时老师并没有将this的用法解释清楚,就迷迷糊糊的将考试糊弄过来,学到的知识点为0。后来在学习c++时遇到this指针,真切地感到熟悉而又陌生!
废话不再讲,让我们开始这部分的知识点吧。
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;
}
int main()
{
Date d1, d2;
d1.Init(2022,1,11);
d2.Init(2022, 1, 12);
d1.Print();
d2.Print();
return 0; }
在上文中提到类多个对象相同的成员函数地址相同,那么比如Date类中的Init函数来讲,对象d1、d2都调用了该函数,函数在调用过程怎么知道是对象d1还是对象d2,该调用对象d1传入的参数还是该调用对象d2传入的参数?
这里就涉及到了c++中隐藏的this指针。
在基层实现中Init()是这样的
void Init(Date* this,int year, int month, int day)
{
this->_year = year;
this->_month = month;
this->_day = day;
}
编译器自动传入对象d1或者是d2的地址,在函数调用时便可以传入相应的参数并对该对象的成员变量进行赋值。
this指针的特性
- this指针的类型:类类型* const,即成员函数中,不能给this指针赋值。
- 只能在“成员函数”的内部使用
- this指针本质上是“成员函数”的形参,当对象调用成员函数时,将对象地址作为实参传递给
this形参。所以对象中不存储this指针。 - this指针是“成员函数”第一个隐含的指针形参,一般情况由编译器通过ecx寄存器自动传
递,不需要用户传递
有关this指针相应的题目及其讲解:
a.this指针存在哪里?
答:由以上this指针的特性可知,this指针时类成员函数的形参,当类成员函数销毁的时候,this指针也不复存在。由此可知,this指针是在函数栈帧中开辟空间,其存储在栈区。
b.this指针可以为空吗?
答:当然可以。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; }
//答:选择C。因为类对象的成员函数存放在常量区(代码块),在被调用的时候是直接去常量区(代码块)调用地址的,而不是在类对象内部去寻找,并不会出现解引用空指针的现象。
// 2.下面程序编译运行结果是? A、编译报错 B、运行崩溃 C、正常运行
class A
{
public:
void PrintA()
{
cout<<_a<<endl;
}
private:
int _a;
};
int main()
{
A* p = nullptr;
p->PrintA();
return 0;
答:选B。因为类对象的成员函数存放在常量区(代码块),在被调用的时候是直接去常量区(代码块)调用地址的,而不是在类对象内部去寻找,并不会出现解引用空指针的现象。但是在函数内部,打印类对象的成员变量的数据,寻找类成员变量就要到类对象去中找,就要对指向对象的指针解引用。此时,指向类对象p的是空指针,出现空指针解引用,会出现运行崩溃。