大家好!从这篇开始,我们就正式进入面向对象了。首先,面向对象我们要先掌握类和对象。类和对象我会分三部分讲解,第一部分,我们先了解一些内容。希望这篇文章对大家所以帮助,大家一起进步!
C语言是面向过程的,关注的是过程,分析出求解问题的步骤,通过函数调用逐步解决问题。
C++是基于面向对象的,关注的是对象,将一件事情拆分成不同的对象,靠对象之间的交互完成。
在C语言中,结构体中只能定义变量:
这里并不是必须加_,习惯加这个,用来标识成员变量。
而在C++里,C++不仅兼容C的struct用法,C++同时对struct进行了升级,把struct 升级成了类,类有以下两点作用:
1、C++结构体名称可以做类型
在C语言中,我们用结构体来定义一个变量。只能这样写:
但是,在C++里,我们就可以直接用名称来做类型:
2、在C++中,结构体内不仅可以定义变量,也可以定义函数。
可能有人会问,为什么_name,_genger,_age,它们这些成员变量在下面定义的,上面为什么能用,其实这里C++将类看作了一个整体,所以这些成员变量定义在哪都可以。
上面结构体的定义,在C++中更喜欢用class来代替struct。
固定的格式如下:
class为定义类的关键字,ClassName为类的名字,{}中为类的主体,注意类定义结束时后面分号。
类中的元素称为类的成员:类中的数据称为类的属性或者成员变量; 类中的函数称为类的方法或者成员函数。
C++实现封装的方式:用类将对象的属性与方法结合在一块,让对象更加完善,通过访问权限选择性的将其接口提供给外部的用户使用。
当我们把上面的代码改变成class时,它会发生错误:
这是因为:class 不加访问限定符,默认是private(私有的)。
我们给它加上public(公有的)就可以了。
上面的struct为什么就可以呢?这是因为:
struct 不加访问限定符,默认是public
然后,我们也可以想公有的就公有,想私有的就私有。
public到下一个限定符就结束,而private到这个收括号结束。
访问限定符说明:
1.public修饰的成员在类外可以直接被访问
2.protected和private修饰的成员在类外不能直接被访问(此处protected和private是类似的,不同点在继承再说)
3.访问权限作用域从该访问限定符出现的位置开始直到下一个访问限定符出现时为止
4.class的默认访问权限为private,struct为public(因为struct要兼容C)
注意:访问限定符只在编译时有用,当数据映射到内存后,没有任何访问限定符上的区别
面试题:C++中struct和class的区别是什么?
C++需要兼容C语言,所以C++中struct可以当成结构体去使用。另外C++中struct还可以用来定义类。和class是定义类是一样的,区别是struct的成员默认访问方式是public,class是的成员默认访问方式是private。
面向对象的三大特性:封装、继承、多态。
在类和对象阶段,我们只研究类的封装特性,那什么是封装呢?
封装:将数据和操作数据的方法进行有机结合,隐藏对象的属性和实现细节,仅对外公开接口来和对象进行交互。
为什么要有封装呢?这是因为在C语言里:数据和方法是分离的。这样就会太自由了。为什么太自由了呢?我举个例子:
前面我们用过C语言写过数据结构,在初始化时有两种方式:
一个初始化为栈顶元素,一个为栈顶元素下一个位置。
如果,我们要取栈顶元素,按照正规的写法,是调用函数:
printf("%d\n", StackTop(&st));
但不妨碍有些人会这样去写:
printf("%d\n", st._a[st._top]); // 可能就存在误用
printf("%d\n", st._a[st._top-1]); // 可能就存在误用
当使用者不知道是如何初始化的,这样写就会发生误区。
那么C++是如何解决的呢?就是用到封装:
1、数据和方法封装到一起,在类里面
在类里面,我们每个方法的结构可以不用传过来了。
2、想给你自由访问的设计成共有,不想给你直接访问的设计成私有
这样,你在C++里就只能使用函数接口了,不能再直接访问成员变量了。
所以,一般情况下:
一般情况设计类,成员数据都是私有或者保护,想给访问的函数是共有,不想给你访问的函数是私有或保护。
类定义了一个新的作用域,类的所有成员都在类的作用域中。
在类体外定义成员,需要使用 :: 作用域解析符指明成员属于哪个类域。
举个例子:
以后,我们在C++里定义头文件就应该定义类了。
在.CPP里进行函数的定义:
如果还是按照C语言方式去写,就不行了。必须要指定类的作用域:
可能有人会问不是不能访问私有的吗?
访问限定符是防止在类外访问,这里已经指定了在类里了,可以访问。
在这里,再说一个小点:函数也可以在类里面定义
在类里面定义的函数默认是inline。
总结一下:实际中,一般情况下,短小函数可以直接在类里面定义,长一点函数声明和定义分离。
用类类型创建对象的过程,称为类的实例化。
1.类只是一个模型一样的东西,限定了类有哪些成员,定义出一个类并没有分配实际的内存空间来存储它。
2.一个类可以实例化出多个对象,实例化出的对象占用实际的物理空间,存储类成员变量。
像这些成员变量,并没有分配实际的内存空间来存储它。只能实例化出的对象,才会分配实际的内存空间来存储它。
这样才会分配实际的内存空间。
类中既可以有成员变量,又可以有成员函数,那么一个类的对象中包含了什么?如何计算一个类的大小?
对象中包含类的各个成员
缺陷:每个对象中成员变量是不同的,但是调用同一份函数,如果按照此种方式存储,当一个类创建多个对象时,每个对象中都会保存一份代码,相同代码保存多次,浪费空间。那么如何解决呢?
只保存成员变量,成员函数存放在公共的代码段。
对于上述两种存储方式,那计算机到底是按照那种方式来存储的?
答案是:第二种。
一个类的大小,实际就是该类中”成员变量”之和,当然也要进行内存对齐。
内存对齐如果不知道的同学可以看看我的结构体。
那么如果类中仅有成员函数或者类中什么都没有—空类,结果又是怎么样的呢?
从结果来看,它们不是0,都是1。这是因为:没有成员变量的类对象,编译会给他们分配1byte占位,表示对象存在过。
我们先来定义一个日期类Date:
对于上述类,有这样的一个问题:函数体中没有关于不同对象的区分,那当调用Print函数时,该函数是如何知道应该设置d1对象,而不是设置d2对象呢?
C++中通过引入this指针解决该问题,即:
C++编译器给每个“非静态的成员函数“增加了一个隐藏的指针参数,让该指针指向当前对象(函数运行时调用该函数的对象),在函数体中所有成员变量的操作,都是通过该指针去访问。只不过所有的操作对用户是透明的,即用户不需要来传递,编译器自动完成。
在这里,d1.Print和d2.Print会被编译器编译成这样:
而Print函数会形成这样:
通过this指针来访问每个对象的成员变量。
那么我们可不可以自己来把这些隐藏的东西加上呢?
这样是不行的,我们不能抢了编译器的事情。
1. this指针的类型:类型是:* const
真正的是这个样子:
void Print(Date* const this)
{
}
void Init(Date* const this, int year, int month, int day)
{
}
这是编译器做的,我们实际上不能这样写。
2. 只能在“成员函数”的内部使用
void Print()
{
cout << this->_year << "-" << this->_month << "-" << this->_day << endl;
}
void Init(int year, int month, int day)
{
this->_year = year;
this->_month = month;
this->_day = day;
}
3. this指针本质上其实是一个成员函数的形参,是对象调用成员函数时,将对象地址作为实参传递给this形参。所以对象中不存储this指针。
4. this指针是成员函数第一个隐含的指针形参,一般情况由编译器通过ecx寄存器自动传递,不需要用户传递。
1. this指针存在哪里?
this指针本质上其实是一个成员函数的形参,所以,它一般存在栈中,但有些编译器会使用编译器优化存放到寄存器中。
2. this指针可以为空吗?
此时,我们可以看见this打印了空值,所以this可以为空。
最后,我们来看两道题:
这个是什么呢?我们先来看运行结果:
我们可以看到,运行结果打印出来了。
首先,肯定有人会认为是A. 编译错误。因为,它是这样想的:
p是空指针,那么p->show()就会发生编译错误。其实这样想是错的,因为编译器不可能在编译时就发现它是空指针,一般只能在运行时才能发现。
那么为什么运行时,不报错呢?
因为在编译时,p->show(),只会访问函数的地址,不会解引用p。
所以,可以正常打印。在这里,我们也可以知道此时的this是空值。它其实是这个样子:
这个结果又是什么呢?答案就是B。
它的错误在这里:
此时,this为0,它解引用就会发生错误。