今天讲一下关于C++类和对象的一些知识点:
1.类和对象的初识
C语言是面向过程的,关注过程,分析出求解问题的步骤,通过函数调用逐步解决问题。
C++是基于面向对象的,关注对象,将一件事情拆分成不同的对象,靠对象之间的交互完成。
在C语言中,结构体中只能定义变量,在C++中,结构体内不仅可以定义变量,还可以定义函数。
在C++中更喜欢用class来代替。
2.类的定义
类就是同类事物的抽象集合,而对象就是类的具体实现!
举个例子:
比如军人就属于一个类:
其有很多对象:比如海军,陆军,空军等,,,
class className
{
//类体:由成员变量和成员函数组成
}; //这末尾的分号一定不要忘记
类的两种定义方式:
1.声明和定义全部放在类体中,要注意:成员函数如果在类中定义,编译器可能会将其当成内联函数处理。
2.声明放在.h文件中,类定义放在.cpp文件中,这样声明和定义就分离了,.cpp文件中不仅要引入.h文件还要在定义函数时,函数名前要加上className: : ,一定不能忘记否则编译不会通过。
3.类的访问限定符及封装
3.1访问限定符
访问限定符说明:
1.public修饰的成员在类外可以直接被访问
2.protected和private修饰的成员在类外不能被直接访问
3.访问权限的作用域从该访问限定符出现的位置开始直到下一个访问限定符出现为止
4.class的默认访问权限是private,struct是public(因为struct要兼容C)
注:访问限定符只在编译时有用,当数据映射到内存后,没有任何访问限定符上的区别
3.2 封装
面向对象三大特性:封装,继承,多态 (面试题)
封装:就是将数据和操作的方法进行有机结合,隐藏对象的属性和实现细节,仅对外公开接口来和对象进行交互。
封装本质上是一种管理:我们如何管理兵马俑呢?比如如果什么都不管,兵马俑就被随意破坏了。那么我们 首先建了一座房子把兵马俑给封装起来。但是我们目的全封装起来,不让别人看。所以我们开放了售票通道,可以买票突破封装在合理的监管机制下进去参观。类也是一样,我们使用类数据和方法都封装到一下。 不想给别人看到的,我们使protected/private把成员封装起来。开放一些共有的成员函数对成员合理的访问。所以封装本质是一种管理。
4.类的实例化
用类创建对象的过程叫做类的实例化
1.类就相当于一个模型,限定了有哪些成员,定义出一个类并没有分配实际的内存空间来存储它 。
2.一个类可以实例化出多个对象,实例化出的对象 占用实际的物理空间,存储类成员变量
3.做个比方。类实例化出对象就像现实中使用建筑设计图建造出房子,类就像是设计图,只设计出需要什么东西,但是并没有实体的建筑存在,同样类也只是一个设计,实例化出的对象才能实际存储数据,占用物理空间
5.类的对象模型
5.1如何计算类对象的大小
可以猜测下类对象的存储方式有两种:
1.对象中包含类的各个成员
虽然对象中的成员变量是不同的,但是会调用同一份函数,如果按照此种方式存储,每创建一个对象时,每个对象中都会保存一份代码,相同代码会保存多次,浪费空间,那么如何解决呢?就有了下面这种方式。
2.只保存成员变量,成员函数放在公共代码段
下面以一个例子来证明他是以何种方式存储的:
#include<iostream>
using namespace std;
class A1
{
public:
void f1(){}
private:
int _a;
};
// 类中仅有成员函数
class A2
{
public:
void f2() {}
};
// 类中什么都没有---空类
class A3
{
};
int main()
{
cout<<sizeof(A1)<<endl;
cout<<sizeof(A2)<<endl;
cout<<sizeof(A3)<<endl;
return 0;
}
结果是 4 1 1 ,这就很好说明在计算类的大小时是不包含成员函数的,只包含成员变量。
一个类的大小,实际就是该类中”成员变量”之和,当然也要进行内存对齐,注意空类的大小,空类比较特殊,编译器给了空类一个字节来唯一标识这个类。
内存对齐请见我的另一篇文章,和结构体的内存对齐计算方法是一样的。
https://blog.youkuaiyun.com/Python_programer/article/details/90236722
6.隐含的this指针
例如下面这个例子:
#include<iostream>
using namespace std;
class Date
{
public:
Date(){}
void SetDate(int year,int month,int day){
_year = year;
_month = month;
_day = day;
}
void Display(){
cout <<_year<<"-"<<_month<<"-"<<_day<< endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1;
d1.SetDate(2019, 5, 15);
d1.SetDate(2018, 5, 6);
d1.Display();
d2.Display();
return 0;
}
看到这个代码,你应该想为什么d1和d2都调用SetDate( )和Display( )函数为什么结果还会不i样呢,函数体中并没有关于不同对象的区分,那么当的d1调用SetDate时,该函数是怎么知道要设置d1对象,而不是设置d2对象?
这是因为C++中引入了this指针,C++中编译器给每个成员函数增加了一个隐含的this参数,让该指针指向当前调用该函数的对象,在函数体中所有的成员变量的操作,都是通过该指针去访问的,只不过这些操作对用户是透明的,用户不需要来传递,由编译器自动完成。
比如编译器处理时 :d1.Display() 会变为----》d1.Display(&d1) ,此时Display()函数的形参会变为Display(Date *this) ,*this来接收d1对象的地址,从而函数知道是操作d1对象。
this指针的特性:
1.this指针的类型:类类型* const
2.只能在”成员函数“的内部使用
3.this指针本质其实是一个成员函数的形参,是对象调用成员函数的时候,将对象地址作为实参传递给this形参,所以对象中并不存储this指针。 这也是面试中会被问道的问题,别一口就回答在对象中!!!
4.this指针是成员函数第一个隐含的指针形参,一般情况由编译器通过ecx寄存器自动传递,不需要用户传递
下面来看个面试题:
class A {
public:
void PrintA()
{
cout<<_a<<endl;
}
void Show()
{
cout<<"Show()"<<endl;
}
private:
int _a;
};
int main()
{
Date* p = NULL;
p->PrintA();
p->Show();
}
这个程序会崩溃么,答案当然是会的,为什么呢?
因为看*p对象首先调用的是PrintA( )函数,这时就需要用到我刚才说的this指针了,因为在Print( )函数体中有cout<<_a<<endl; 这样一句话编译器处理时就会变为this->_a,一旦这么做了程序必然崩溃,此时 *p是一个空指针,-> 符号是会涉及到解引用的相当于 *( p ) . ,空指针解引用程序必然崩溃,这才是主要原因!而Show( )函数没牵扯这样的操作,所以只调用Show( )函数的话程序不会出现崩溃!